js 高程 书中原话(斜体表示):
22.1.4 函数绑定
另一个日益流行的高级技巧叫做函数绑定。函数绑定要创建一个函数,可以在特定的this 环境中
以指定参数调用另一个函数。该技巧常常和回调函数与事件处理程序一起使用,以便在将函数作为变量
传递的同时保留代码执行环境。请看以下例子:
var handler = { message: "Event handled", handleClick: function(event) { console.log(this); //改编,方便测试,原文是下面一句 //alert(this.message); } }; document.body.onclick=handler.handleClick; //改编,方便测试,原文是后面两句 //var btn = document.getElementById("my-btn"); //EventUtil.addHandler(btn, "click", handler.handleClick);
看起来,当我们点击 body 的时候,应该输出 handler 这个对象,但是实际上输出的是 body 元素
为什么呢?书上原话:
这个问题在于没有保存 handler.handleClick() 的环境,所以 this 对象最后是指向了 DOM 按钮而非 handler
我理解就是就是,上面的事件绑定代码其实就相当于:
document.body.onclick = function(event) { console.log(this); };
那么这个地方,事件绑定在谁身上, this 当然就指向谁(事件处理程序中的 this 不清楚的点这里),所以这里输出 body 对象也是很合理的,如果要输出 handler 对象,单独执行 handler.handleClick() 就可以了
这里也很好理解,handleClick 作为 handler 对象的方法执行的,那么其中的 this 当然指向的是 handler,结合这种情况,要改上面的代码就要这样:
document.body.onclick = function(event) { console.log(this); //这里的 this 当然还是 body handler.handleClick(); // 但是这里面执行的 this 就是 handler 对象了 };
但是这样写的缺点很明显,原本就是想通过下面一行代码实现的,如图:
document.body.onclick=handler.handleClick; //最初 document.body.onclick = function(event) { //现在 handler.handleClick(); };
现在明显繁琐了些,而且最初的一行代码理论上也应该是可以实现的,所以现在的代码需要优化封装下,来达到同样的效果,封装的时候,要实现两个目的
- 调用函数的时候执行 handler.handleClick(); 且要保存它的执行环境(也就是要确定执行时的 this ,指向的是 handler,而不会变成其他的对象)
- 要把 event 对象传递过去
看到需要改变 this 和保存执行环境的时候,应该想到 js 的函数都有内置的 apply(),call()方法,且 arguments 对象都保存了函数的参数(这个不理解可以看 这里),所以这里封装函数也不是很难了,假设封装的函数名叫 bind ,整个封装逻辑就是下面这样:
//执行 bind 函数需要返回的匿名函数 function(event) { handler.handleClick(); }; //抽象化 function(event) { fn(); } //推算 bind 函数的原型(此原型非js中的原型) function bind(fn) { return function(event) { fn(); }; } //为了保证 this 的正确传递,需要用 apply 方法,这里用法不理解,请看 这里 function bind(fn,context) { return function(event) { fn.apply(context,参数); }; } //为了传递 event 参数,这里的参数不太好理解,需要好好想想 function bind(fn,context) { return function() { fn.apply(context,arguments); }; }
书上原文的结果也是一样的:
bind 函数 推导出来后,就简单了,原来的代码就变成了
document.body.onclick=handler.handleClick; //最初错误代码 document.body.onclick = function(event) { //后来改善代码 handler.handleClick(); }; document.body.onclick = bind(handler.handleClick,handler); //最后封装 bind 函数
点击 body 后,就返回的 handler 对象,这样就可以访问 handler 的属性了;
不过,ES5 已经为所有函数定义了一个原生的 bind() 方法,进一步简单了操作,像上面的代码用 ES5 实际上就是一句
document.body.onclick = handler.handleClick.bind(handler);
所以前面都是废话,- -