函数绑定
在前面我们已经知道setTimeout()很容易就会丢失this,看下面的例子:
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(user.sayHi, 1000); // Hello, undefined!
这里this.firstName的值为undefined,因为setTimeout()在接收user.sayHi()时与user对象是隔离的,故this就丢失了。它类似于一下的操作:
let f = user.sayHi;
setTimeout(f, 1000); // lost user context
因为在浏览器中运行,所以丢失了上下文对象user后,this所指向的对象就是全局对象window,故为undefined
解决方案
(1)使用包装器
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(function() {
user.sayHi(); // Hello, John!
}, 1000);
setTimeout的第一个参数直接使用user.sayHi(),此时setTimeout就会根据词法环境接受user对象作为上下文对象,下面是简写的例子:
setTimeout(() => user.sayHi(), 1000); // Hello, John!
但是有一个问题要注意的是,如果setTimeout要调用的执行函数内容在调度前被修改,那么setTimeout触发的执行函数为修改过的内容,例如:
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(() => user.sayHi(), 1000);
// ...within 1 second
user = { sayHi() { alert("Another user in setTimeout!"); } };
// Another user in setTimeout?!?
为了结果这个问题,我们需要用bind()来绑定上下文对象
(2)bind()
Javascript的函数都内置了bind()方法来绑定上下文对象,它的语法如下:
// more complex syntax will be little later
let boundFunc = func.bind(context);
bind()默认返回修改过上下文对象(this=context)的新函数(boundFunc)
看下面的例子:
let user = {
firstName: "John"
};
function func(phrase) {
alert(phrase + ', ' + this.firstName);
}
// bind this to user
let funcUser = func.bind(user);
funcUser("Hello"); // Hello, John (argument "Hello" is passed, and this=user)
上述例子中,当我们调用funcUser(...)的时候,它会去调用func()并且修改上下文对象this=context
现在我们尝试绑定对象方法,例如:
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
let sayHi = user.sayHi.bind(user); // (*)
sayHi(); // Hello, John!
setTimeout(sayHi, 1000); // Hello, John!
如果一个对象有许多的方法需要绑定上下文对象,我们可以使用bindAll()来绑定所有的方法,或者我们可以遍历对象的所有属性方法来绑定this,例如:
for (let key in user) {
if (typeof user[key] == 'function') {
user[key] = user[key].bind(user);
}
}