本系列作为Effective JavaScript的读书笔记。
当需要将方法抽取出来作为回调函数使用的时候,常常会因为this的指向不明而发生错误,比如:
var buffer = { entries: [], add: function(s) { this.entries.push(s); }, concat: function() { return this.entries.join(""); } };
如果想利用其中的add作为回调函数对一组数据进行添加:
var source = ["867", "-", "5309"]; source.forEach(buffer.add); // error: entries is undefined
以上的forEach方法时ES5中引入的,如果不是使用的ES5环境,可以使用lodash或者underscore库中的同名方法。
在Item 18中我们知道了当调用一个函数的时候,函数体中this的指向是由该函数的调用方式决定的,比如当这样调用时:buffer.add(something),this的指向就是buffer对象。
然而当add方法作为回调函数在forEach中使用时,仅仅是将该它单纯地作为函数进行调用,所以this指向的是window或者undefined(根据是否使用strict mode而不同)。
此时,需要将this的指向也作为第二个参数传入到forEach中:
var source = ["867", "-", "5309"]; source.forEach(buffer.add, buffer); buffer.join(); // "867-5309"
但并不是所有的高阶函数都像forEach这样能够让你传入一个this的指向,此时可以采用这种方式:
var source = ["867", "-", "5309"]; source.forEach(function(s) { buffer.add(s); }); buffer.join(); // "867-5309"
上述代码创建了一个匿名函数,将add作为方法调用,就保证了this的指向是buffer对象。
因为在某些时候,函数中this的指向是固定的,所以为了支持这种场景,ES5中添加了bind方法:
var source = ["867", "-", "5309"]; source.forEach(buffer.add.bind(buffer)); buffer.join(); // "867-5309"
它的实现原理就是上面使用匿名函数的方式,bind方法本身是一个高阶函数,它会根据传入的对象,返回另一个函数来当做回调函数(也许是这样的,原文中并没有讲述bind的实现,如有不妥,希望能够帮我指正):
function bind(receiver) { var fn = this; return function(s) { receiver.fn(s); }; }
所以,bind方法本身不会修改被"装饰"的方法,而是在生成了一个新的方法用来完成this的指向。可以通过下面的代码来验证这一点:
buffer.add === buffer.add.bind(buffer); // false
这也意味着,在任何函数上使用bind都是安全的,比如被共享的函数和原型继承链上的函数,它不会修改原函数的行为,而是生成了新的函数。
总结: