函数绑定
当将对象方法作为回调进行传递,例如传递给 setTimeout,这儿会存在一个常见的问题:“丢失 this”。
在本篇中,我们会学习如何去解决这个问题。
丢失 “this”
我们已经看到了丢失 this
的例子。一旦方法被传递到与对象分开的某个地方 —— this 就丢失。
下面是使用 setTimeout 时 this 是如何丢失的:
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(user.sayHi, 1000); // Hello, undefined!
正如我们所看到的,输出没有像 this.firstName 那样显示 “John”,而显示了 undefined!(在严格模式下)
这是因为 setTimeout 获取到了函数 user.sayHi,但它和对象分离开了。最后一行可以被重写为:
let f = user.sayHi;
setTimeout(f, 1000); // 丢失了 user 上下文
在本例中f函数实际上只是user.sayHi的一个引用此时调用的f函数使用的是this中的默认绑定。在非严格模式下,这里的this会是window。严格模式下,this将会是undefined。
这个需求很典型 —— 我们想将一个对象方法传递到别的地方(这里 —— 传递到调度程序),然后在该位置调用它。如何确保在正确的上下文中调用它?
解决方案 1:包装器
最简单的解决方案是使用一个包装函数:
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(function() {
user.sayHi(); // Hello, John!
}, 1000);
现在它可以正常工作了,因为它从外部词法环境中获取到了 user,就可以正常地调用方法了。
相同的功能,但是更简短:
setTimeout(() => user.sayHi(), 1000); // Hello, John!
看起来不错,但是我们的代码结构中出现了一个小漏洞。
如果在 setTimeout 触发之前(有一秒的延迟!)user 的值改变了怎么办?那么,突然间,它将调用错误的对象!
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(() => user.sayHi(), 1000);
// ……user 的值在不到 1 秒的时间内发生了改变
user = {
sayHi() { alert("Another user in setTimeout!"); }
};
// Another user in setTimeout!
下一个解决方案保证了这样的事情不会发生。
解决方案 2:bind
函数提供了一个内建方法 bind,它可以绑定 this。bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。
基本的语法是: