回调与作用域
在前面的例子中,执行回调的函数一般都如下所示:
而回调一般都如下所示:
这在一般情况下都可以正常的工作,但在某些时候,回调并不仅仅是单一的函数,而是某个对象的方法,并且如果该回调使用 this 来引用其所属的对象的话,就会导致错误发生。
假如有一个 add 的回调函数,它是 app 对象的一个方法:
var app = {};
app.number = 1;
app.add = function (n) {
n = n + this.number;
};
makeArray 函数做了类似下面的事:
var makeArray = function (callback) {
// ...
if (typeof callback === "function") {
callback(i);
}
// ...
};
调用
当调用的时候,它并不能按照预期的那样工作,因为 this.number 将会是 undefined。这里 this 将会指向全局对象,因为 makeArray 是一个全局函数。
如果 makeArray 是某个对象的方法,那么在回调函数中的 this 将会指向这个对象而不是期望的 app。(详情请了解下 javascript 中的 this)
解决这个问题的方法就是传递一个回调函数,此外再传递这个回调函数属于的对象作为一个参数:
紧跟着,我们需要去修改 makeArray 去绑定(bind)传递进来的对象:
var makeArray = function (callback, callback_obj) {
//...
if (typeof callback === "function") {
callback.call(callback_obj, i);
}
// ...
};
对于传递一个对象和一个被用来回调的方法,另一个可选的方法就是将方法作为字符串传递,那么你就不会重复对象两次。换言之:
会变成:
那么 makeArray 将会变为如下面几行所示:
var makeArray = function (callback, callback_obj) {
if (typeof callback === "string") {
callback = callback_obj[callback];
}
//...
if (typeof callback === "function") {
callback.call(callback_obj, i);
}
// ...
};
匿名的事件监听器
回调模式在日常中被经常使用,比如,当你附加一个事件监听器给页面上的某个元素时,你实际上提供了一个指向了回调函数的引用,并且在事件发生时被调用。
这里有个例子,怎么将 console.log() 作为一个回调函数监听文档的 click 事件:
document.addEventListener("click", console.log, false);
绝大部分客户端浏览器都是事件驱动的(event-driven),当一个页面加载完成,会触发load事件,然后用户可以通过和页面交互触发各种各样的事件,比如:click, keypress, mouseover, mousemove等等,因为回调模式,JavaScript特别适合事件驱动编程,能让你的程序异步的工作,换言之,就是不受顺序限制。
在异步的事件驱动的JavaScript,可以提供一个回调函数用于在正确的时候被调用;有时候甚至可能提供比实际请求还要多的回调函数,因为某些事件可能不会发生,比如:如果用户不点击“购买”按钮,那么你用于验证表单格式的函数永远不会被调用。
定时
另一个使用回调模式的例子就是使用浏览器的window对象的setTimeout()和setInterval()方法,这些方法也可以接受和执行回调函数:
var thePlotThickens = function () {
console.log('500ms later...');
};
setTimeout(thePlotThickens, 500);
再次注意一下,thePlotThickens 是如何被作为一个参数传递的,注意这里仍旧没有使用括号,因为你不想它立即执行,传递字符串 “thePlotThickens()” 取代函数的引用和eval()类似,是不好的模式。
当然可以提供匿名函数的方式进行调用。
setTimeout(function(){thePlotThickens()}, 500);