这个
this
对象是函数的上下文。就像你制造了一台机器来为你做某些事情,这个
this
对象就像是机器工作的地方,比如你的房子。你可以根据需要移动它。
我们有4种设置
this
对象的方式。
调用非方法函数:
fn(someArguments)
这样,
this
对象将被设置为 null 或者可能是 window 对象。
作为方法调用函数:
someObject.fn(someArguments)
在这种情况下,
this
对象将指向
someObject
并且它是可变的。
使用函数的 call
或 apply
方法进行调用。
fn.call(anotherObject, someArguments)
someObject.call(anotherObject, someArguments)
someObject.apply(anotherObject, [someArguments])
在这种情况下,
this
对象将指向
someObject
。当调用它时,您正在强制它具有另一个上下文。
绑定函数
var fn2 = fn.bind(anotherObject, someArguments)
这将创建另一个绑定到我们给它的
this
对象(
anotherObject
)的函数。无论如何调用它,
this
对象都将是相同的。
用例
现在您可以进行一些巧妙的操作。之所以我们在这里使用它(我认为它最初来自C ++),是因为对象的方法需要访问其父级。
this
对象提供了访问权限。
var coolObject = {
points : ['People are amazing'],
addPoint : function (p) { this.points.push(p) }
}
所以,如果你按照以下方式做,它不会起作用:
var addPoint = coolObject.addPoint;
addPoint('This will result in an error');
错误将被抛出,因为此时的 this 对象不再是我们的
coolObject
,并且没有 points 属性。因此在这种情况下,你可以像这样处理:
var addPoint = coolObject.addPoint;
addPoint.call({points : []}, 'This is pointless');
这是毫无意义的,但是即使this
对象不是它应该是的,函数也能正常工作。
var anotherCoolObject = {
points : ['Im a thief!'],
addPoint : coolObject.addPoint
}
anotherCoolObject.addPoint('THIS IS CALL STEALING');
即使这样调用函数,函数仍然可以正常工作,因为this
对象将指向另一个具有points
属性的对象。
我见过的最常见的用例是对参数对象进行切片:
function returnHalf() {
return [].slice.call(arguments, 0, arguments.length / 2);
}
returnHalf('Half', 'is', 'not', 'awesome');
所以你看,arguments对象不是数组的一个实例。如果我们执行arguments.slice(...)
,那么编译器会报错。但是这里我们使用了数组的方法来处理arguments对象,因为它类似于数组。
有时候你不想改变函数上下文或者想要添加自己的参数,那么你可以使用bind。
例如,当你使用jquery为事件添加监听器时,当jquery调用你的函数时,this对象将是元素。但有时候你想做一些巧妙的事情并更改它:
var myElement = {
init : function () {
$(this.element).click(this.listener.bind(this));
},
view : "<li>${Name}</li>",
name : 'ed',
element : $('#myelement'),
listener : function () {
this.element.append($.tmpl( this.view, this ));
}
}
myElement.init();
在这里,你将它绑定到myElement,这样就可以访问对象属性以渲染视图。另一个例子如下:
for (var i = 0; i < 10; i++) {
setTimeout(function () {console.log(i)}, 10)
}
for (var i = 0; i < 10; i++) {
setTimeout((function () {console.log(this.i)}).bind({ i : i }, 10)
}
如果您在循环中放置了一个异步函数调用,当回调被调用时,循环已经完成,并且计数器已经达到了末尾,您可以使用bind将当前计数器干净地绑定到回调函数上。
另一个很好的用例是,当我将带有参数的函数传递给
async
模块时,而不创建闭包。
async.parallel({
writeFile : function (cb) {
fs.writeFile('lolz.txt', someData, cb);
},
writeFile2 : function (cb) {
fs.writeFile('lolz2.txt', someData, cb);
}
}, function (err){
console.log('finished')
});
async.parallel({
writeFile : fs.writeFile.bind(fs, 'lolz.txt', someData),
writeFile2 : fs.writeFile.bind(fs, 'lol2z.txt', someData),
}, function (err){
console.log('finished')
});
这两个实现是相同的。
性能
只需检查这些:
http://jsperf.com/bind-vs-call2
http://jsperf.com/js-bind-vs-closure/2
http://jsperf.com/call-vs-closure-to-pass-scope/10
bind
相比其他调用类型有较大的性能开销,但要确保在进行过早优化时不会牺牲维护性能。
此外,您可以查看this文章。