回调函数

一个知乎上的比喻:
你到一个商店买东西,刚好你要的东西没有货,于是你在店员那里留下了你的电话,过了几天店里有货了,店员就打了你的电话,然后你接到电话后就到店里去取了货。在这个例子里,你的电话号码就叫回调函数,你把电话留给店员就叫登记回调函数,店里后来有货了叫做触发了回调关联的事件,店员给你打电话叫做调用回调函数,你到店里去取货叫做响应回调事件。
简而言之,回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用它所指向的函数时,我们就说这是回调函数。

函数指针

函数指针就是函数的地址, 回调函数是函数指针的应用.
一个函数指针是一个指针变量了,它所指向的是一个函数,它的值就是所指向函数的入口地址。

python中的回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def my_callback(input):
print "function my_callback was called with %s input" % (input,)
def caller(input, func):
func(input)
for i in range(5):
caller(i, my_callback)
>>>
function my_callback was called with 0 input
function my_callback was called with 1 input
function my_callback was called with 2 input
function my_callback was called with 3 input
function my_callback was called with 4 input

js中的回调函数

JavaScript中,回调函数具体的定义为:函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行函数A。我们就说函数A叫做回调函数。如果没有名称(函数表达式),就叫做匿名回调函数。
因此callback 不一定用于异步,一般同步(阻塞)的场景下也经常用到回调,比如要求执行某些操作后执行回调函数。
例子1: 同步(阻塞)中使用回调,目的是在func1代码执行完成后执行func2。

1
2
3
4
5
6
7
8
var func1=function(callback){
//do something.
(callback && typeof(callback) === "function") && callback();
}
func1(func2);
var func2=function(){
}

例子2: 异步回调

1
2
3
4
5
6
7
8
9
10
11
12
13
$(document).ready(callback);
$.ajax({
url: "test.html",
context: document.body
}).done(function() {
$(this).addClass("done");
}).fail(function() { alert("error");
}).always(function() { alert("complete");
});
/**
注意的是,ajax请求确实是异步的,不过这请求是由浏览器新开一个线程请求,当请求的状态变更时,如果先前已设置回调,这异步线程就产生状态变更事件放到 JavaScript引擎的处理队列中等待处理。见:http://www.phpv.net/html/1700.html
*/

回调什么时候执行

回调函数,一般在同步情境下是最后执行的,而在异步情境下有可能不执行,因为事件没有被触发或者条件不满足。

回调函数的使用场合

  • 资源加载:动态加载js文件后执行回调,加载iframe后执行回调,ajax操作回调,图片加载完成执行回调,AJAX等等。
  • DOM事件及Node.js事件基于回调机制(Node.js回调可能会出现多层回调嵌套的问题)。
  • setTimeout的延迟时间为0,这个hack经常被用到,settimeout调用的函数其实就是一个callback的体现
  • 链式调用:链式调用的时候,在赋值器(setter)方法中(或者本身没有返回值的方法中)很容易实现链式调用,而取值器(getter)相对来说不好实现链式调用,因为你需要取值器返回你需要的数据而不是this指针,如果要实现链式方法,可以用回调函数来实现
  • setTimeout、setInterval的函数调用得到其返回值。由于两个函数都是异步的,即:他们的调用时序和程序的主流程是相对独立的,所以没有办法在主体里面等待它们的返回值,它们被打开的时候程序也不会停下来等待,否则也就失去了setTimeout及setInterval的意义了,所以用return已经没有意义,只能使用callback。callback的意义在于将timer执行的结果通知给代理函数进行及时处理。

回调函数的传递

回调函数的传递是通过将函数引用或者函数表达式作为参数传递。

1
2
3
4
5
$.get('myhtmlpage.html', myCallBack);//这是对的
$.get('myhtmlpage.html', myCallBack('foo', 'bar'));//这是错的,那么要带参数呢?
$.get('myhtmlpage.html', function(){//带参数的使用函数表达式
myCallBack('foo', 'bar');
});

另外,最好保证回调存在且必须是函数引用或者函数表达式: (callback && typeof(callback) === “function”) && callback();

如何使用回调函数

使用回调函数,我们需要做三件事:

  1. 声明
  2. 定义
  3. 设置触发条件:在你的函数种把你的回调函数名称转化为地址作为一个参数,以便于系统调用。
    声明和定义时应注意,回调函数由系统调用,所以可以认为它属于windows系统,不要把它当作你的某个类的成员函数。
    回调函数是一个程序员不能显示调用的函数,通过将回调函数的地址传给调用者从而实现调用。回调函数是十分有必要的,在我们想通过一个统一接口实现不同的内容,这时回调函数非常合适。

调用约定

在visual c++中,可以在函数类型前加_cdecl,_stdcall或者_pascal来表示调用规范(默认为_cdecl)。调用规范影响编译器产生的给定函数名,参数传递的顺序,堆栈清理责任以及参数传递机制。
不过,在win32的程序中,我见得比较多的是CALLBACK,这个宏定义在windef.h中,
#define CALLBACK __stdcall 它约定了函数在它们返回到调用者之前,都会从堆栈中移除掉参数。

事实上回掉函数和普通函数是没区别的. 你同样可以使用定义的回调函数做其它事情. 调用约定是有调用者规定的(函数参数中规定了回调函数的类型, 函数类型包括了函数的调用约定, 参数, 返回值), CALLBACK 是 win16 一个遗留宏定义, 当初是定义成的 pascal 约定, 现在都是定义成 WINAPI宏, 使用的是 __stdcall 约定.
类静态成员函数可以作回调函数, 不用成员函数做回调函数的原因是成员函数的参数比参数表中列出的多了一个this指针(其实也是可以用的, 那种技巧没什么好处)

参考