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
属性的CoolObject。
我见过的最流行的用例是对 arguments 对象进行切片:
function returnHalf() {
return [].slice.call(arguments, 0, arguments.length / 2);
}
returnHalf('Half', 'is', 'not', 'awesome');
// >> [Half', 'is']
所以你看,arguments 对象不是一个 instanceof 数组。如果我们这样做,arguments.slice(...)
那么您将被编译器杀死。但是这里我们在 arguments 对象上使用数组的方法,因为它像数组。
有时你不想改变你的函数上下文或者你想添加你自己的参数,你使用绑定。
例如,当您使用 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)
}
// All of them will be 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
与其他类型的调用相比,具有很大的性能开销,但请确保您不会因未成熟的优化而牺牲可维护性的性能。
你也可以看看这篇文章。