回调函数模式
回调函数与控制反转
回调函数是程序设计的一种方法。这种方法是指,在传递了可能会进行调用的函数或对象之后,在需要时再分别对其进行调用。由于调用方与被调用方的依赖关系与通常相反,所以也成为控制反转(IoC,Inversion of Control)。
由于历史原因,在JavaScript开发中我们常常会用到回调函数这一方法,这是多种因素导致的。第一个原因是在客户端JavaScript中基本都是GUI程序设计。GUI程序设计是一种很适合使用所谓事件驱动的程序设计方式。事件驱动正是一种回调函数设计模式。客户端JavaScript程序设计是一种基于DOM的事件驱动式程序设计。
第二个原因是,源于客户端无法实现多线程程序设计(最近HTML5 Web Works支持多线程了)。而通过将回调函数与异步处理相结合,就能够实现并行处理。由于不支持多线程,所以为了实现并行处理,不得不使用回调函数,这逐渐成为了一种惯例。最后一个原因与JavaScript中的函数声明表达式和闭包有关。
JavaScript与回调函数
var emitter = {
// 为了能够注册多个回调函数而通过数组管理
callbacks:[],
// 回调函数的注册方法
register:function (fn) {
this.callbacks.push(fn);
},
// 事件的触发处理
onOpen:function () {
for (var f in this.callbacks) {
this.callbacks[f]();
}
}
};
emitter.register(function () {alert("event handler1 is called");})
emitter.register(function () {alert("event handler2 is called");})
emitter.onOpen();
// "event handler1 is called"
// "event handler2 is called"
定义的两个匿名函数就是回调函数,它们的调用由emitter.onOpen()
完成。
对emitter来说,这仅仅是对注册的函数进行了调用,不过根据回调函数的定义,更应该关注使用了emitter部分的情况。从这个角度来看,注册过的回调函数与之形成的是一种调用与被调用的关系。
上面的回调函数只是单纯的函数而不具有状态。如果回调函数具有状态,就能得到更为广泛的应用。下面我们把回调方改为了对象,于是emitter变为了接受方法传递的形式。
function MyClass(msg) {
this.msg = msg;
this.show = function () {alert(this.msg+' is called');}
}
// 将方法注册为回调函数
var obj = new MyClass("listener1");
var obj2 = new MyClass("listener2");
emitter.register(obj.show);
emitter.register(obj2.show);
emitter.onOpen();
// undefined is called
// undefined is called
我们发现,调用回调函数无法正确显示this.msg,错误原因在于JavaScript内的this引用。解决方法有两种,一种是使用bind,一种是不使用方法而是用对象进行注册。后者在JavaScript中并不常用。
emitter.register(obj.show.bind(obj));
emitter.register(obj2.show.bind(obj2));
emitter.onOpen();
// "listener1 is called"
// "listener2 is called"
bind是ES5新增的功能,是Function.prototype对象的方法。bind的作用和apply与call相同,都是用于明确指定出方法调用时的this引用。对于函数来说,调用了bind之后会返回一个新函数,新的函数会执行与原函数相同的内容,不过其this引用是被指定为它的第一个参数的对象。在调用apply与call时将会立即调用目标函数,而在调用bind时则不会如此,而是会返回一个函数(闭包)。
如果使用了apply或call,就能对bind进行独立的实现。事实上在ES5才推出之前,在prototype.js等知名的库中就通过apply/call提供了bind自己的实现。
脑补的bind的内部实现?
Function.prototype.bind = null;
Function.prototype.bind = function (obj) {
var f = this;
return function () {
f.call(obj);
}
}
var obj = {
x:"这是 obj.x !!!",
fn:function () {
alert(this.x);
}
};
var obj2 = {x:"obj2.x 对啦!!!"};
var testfn = obj.fn.bind(obj2);
testfn(); // "obj2.x 对啦!!!"
闭包与回调函数
emitter.register(
(function () {
var msg = "closure1";
return function () {alert(msg+" is called;")};
}())
);
emitter.register(
(function () {
var msg = "closure2";
return function () {alert(msg+" is called;")};
}())
)
emitter.onOpen();
// "closure1 is called"
// "closure2 is called"
借助闭包,前面繁复的说明仿佛不在存在,可以很轻松的实现回调函数,并且还能像对象一样具有状态。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。