当将对象方法作为回调进行传递,例如传递给 setTimeout
,这儿会存在一个常见的问题:“丢失 this
”。
例如
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(user.sayHi, 1000); // Hello, undefined!
浏览器中的 setTimeout
方法有些特殊:它为函数调用设定了 this=window
(对于 Node.js,this
则会变为计时器(timer)对象,但在这儿并不重要)。所以对于 this.firstName
,它其实试图获取的是 window.firstName
,这个变量并不存在。在其他类似的情况下,通常 this
会变为 undefined
。
setTimeout(user.sayHi, 1000);
获取到了对象里的函数,但是函数与对象分离了,这里的this变成了window
解决方案一:包装器
最简单的解决方案是使用一个包装函数:
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(function() {
user.sayHi(); // Hello, John!
}, 1000);
现在它可以正常工作了,因为它从外部词法环境中获取到了 user
,就可以正常地调用方法了。
相同的功能,但是更简短:
setTimeout(() => user.sayHi(), 1000); // Hello, John!
所有的函数在“诞生”时都会记住创建它们的词法环境
使用包装器,创建了一个新的函数
使用包装器的缺点是如果user对象被重新赋值,函数调用也跟着改变
解决方案二:bind
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
let sayHi = user.sayHi.bind(user);
sayHi(); // Hello, John!
setTimeout(sayHi, 1000); // Hello, John!
user = {
sayHi() { alert("Another user in setTimeout!"); }
};
bind创建了一个新的函数,即时user对象里的sayHi函数被改变了也没关系
解决方案三:使用类字段制作绑定方法
class User {
constructor(name){
this.firstName = name
}
sayHi = () => {
alert(`Hello, ${this.firstName}!`)
}
}
let user = new User('John')
setTimeout(user.sayHi, 1000)
使用类字段创建的方法,this总是指向其对象
此方法在React事件监听时常用