前言
最近在看Node.js,有介绍一些关于回调函数的内容,之前在js里面有用过,但是只是知道要那样用,为什么要这样做和这样做有什么用处并不清楚。自己查了一些资料,理解了一下,记录下来作为回顾也方便之后复习。
一、什么是回调函数?
A callback is a function that is passed as an argument to another function and is executed after its parent function has completed.
例如:
function Cat(words, callback) {
callback(words);
}
function Speak(words) {
console.log(words);
}
Cat("miao", Speak);
Cat("miao", function(words) {
console.log(words);
});
上述代码Cat中的Speak函数和匿名函数就被称为回调函数。
然后再来看一段代码:
function f1() {
console.log("f1 finished.");
}
function f2(cb) {
cb();
console.log("f2 finished.");
}
//执行结果: f1 finished.
// f2 finished.
那么问题来了,不是说回调函数最后执行吗????
对,很多介绍回调函数的例子讲到这里是就完了,异步回调函数的确是应该在函数的最后执行,不过上面的例子是一个同步回调函数,函数的执行顺序依然自上而下顺序执行。 那么什么是异步回调呢? 我们又怎么实现异步回调呢? 请往下看。
异步回调函数:
function f2() {
console.log('f2 finished');
}
function f1(cb) {
setTimeout(cb,1000); //用setTimeout()模拟耗时操作
console.log('f1 finished');
}
f1(f2); //得到的结果是 f1 finished ,f2 finished
因为setTimout()是异步函数,所以这里是先将f1执行完后再执行的回调函数f2。
小结:因为函数在Javascript中是第一类对象,我们像对待对象一样对待函数,因此我们能像传递变量一样传递函数,在函数中返回函数,在其他函数中使用函数。当我们将一个回调函数作为参数传递给另一个函数是,我们仅仅传递了函数定义。我们并没有在参数中执行函数。【需要注意的很重要的一点是回调函数并不会马上被执行。它会在包含它的函数内的某个特定时间点被“回调”。就好像它是在这个函数里面定义的,这意味着回调函数本质上是一个闭包。正如我们所知,闭包能够进入包含它的函数的作用域,因此,回调函数能获取包含它的函数中的变量,以及全局作用域中的变量。】最后,强调一点,并不是使用了回调函数就是异步,回调函数只是异步的一种实现方式而已。
二、为什么要使用回调函数?
可能有人想问,那为什么不直接从Cat函数里面调用Speak呢???如果你直接在函数Cat里调用的话,那么这个回调函数就被限制死了。但是使用函数做参数就有下面的好处:当你Cat(Speak)的时候函数Speak就成了回调函数,而你还可以Cat(Smell)这个时候,函数Smell就成了回调函数。如果你写成了function Cat(){…;Speak();}就失去了变量的灵活性。
另外一点是,当函数的实现过程非常漫长,你是选择等待函数完成处理,还是使用回调函数进行异步处理?这种情况下,使用回调函数变得至关重要,例如:AJAX请求,。若是使用回调函数进行处理,代码就可以继续进行其他任务,而无需空等。实际开发中,经常在javascript中使用异步调用。
三、使用回调函数应该注意些什么?
1.使用命名或匿名函数作为回调
2.在执行之前确保回调函数是一个函数
3.使用this对象的方法作为回调函数时的问题:
当回调函数是一个this对象的方法时,我们必须改变执行回调函数的方法来保证this对象的上下文。否则如果回调函数被传递给一个全局函数,this对象要么指向全局window对象(在浏览器中)。要么指向包含方法的对象。
例如:
var localData = {
id: 094545,
name :"nothing",
//setUsrName是一个在clientData对象中的方法
setName: function (name){
//这指向了对象中的fullName属性
this.name = name;
}
}
function getName(name, callback){
callback(name);
}
getName("Tom",localData.setName);
console.log(localData.name); //nothing
console.log(window.name); //Tom
当你执行getName函数时,因为getName函数是一个全局函数,所以它上下文是getName的上下文,是全局window对象,所以它将设置window对象的name属性为“Tom”。
4.使用Call和Apply函数来保存this:
为了解决3的问题,我们通常使用apply或者call。当需要用到this的时候,我们只需在函数中加一个参数,以确定我们的上下文对象。例如:
function successCb() {
//...
}
function errorCb() {
//...
}
$.ajax({
type:"get",
url:"http://localhost/index.php",
data:{uid:id,password:password},
dataType:"jsonp",
jsonp:"callback",
success:successCb,
error:errorCb,
});
四、总结
在Javascript编程中回调函数经常以几种方式被使用,尤其是在现代web应用开发以及库和框架中:
1)异步调用(例如读取文件,进行HTTP请求,等等)
2)时间监听器/处理器
3)setTimeout和setInterval方法
4)一般情况:精简代码