Javascript函数绑定
当我们在
setTimeout
函数中使用对象方法或传递对象方法,会出现丢失this
的问题。this突然停止工作,这个场景对新手来说很常见,即使有经验的开发这也会遇到。
失去this
我们已经知道在Javascript中很容易丢失this
,一旦一个方法被传递值和对象分离的地方时,this
丢失。
这里看看在setTimeout
中是如何发生的:
let user = {
firstName: "John",
sayHi() {
alert(`Hello, ${this.firstName}!`);
}
};
setTimeout(user.sayHi, 1000); // Hello, undefined!
我们看到,输出this.firstName
的结果并不是“John”,而是underfined
。
那是因为setTimeout
获得函数user.sayHi
,其和所属对象分离了,最后一行也能被重新成这样:
let f = user.sayHi;
setTimeout(f, 1000); // lost user context
浏览器内置方法setTimeout
有点特殊:函数调用时其设置this=window
(Node.JS中this为timer对象),所以this.firstName
即为window.firstName
,结果不存在。很其他我们将看到情况类似,通常this
变为underfined
.
很典型的任务是我们想传递对象方式至其他地方执行(这里是计划执行),如何确保其在正确的上下文中被调用。
方案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
的值变化了(因为有1秒延迟),会怎样?那么,它将调用错误的对象!
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?!?
下一个解决方案保证这样的事情不会发生。
方案2:绑定
函数提供了一个内置方法bind
,允许我们修改this。基本的语法为:
// more complex syntax will be little later
let boundFunc = func.bind(context);
调用func.bind(context)
的结果是,指定一个外部对象作为可调用函数的上下文,设置this=context
.
换句话说,调用绑定函数就如func
带了固定的this
。
举例,这里funcUser
传递一个调用给func
,同时指定this=user
:
let user = {
firstName: "John"
};
function func() {
alert(this.firstName);
}
let funcUser = func.bind(user);
funcUser(); // John
这里func.bind(user)
如func
的绑定变量,并带有this=user
.
所有其他参数被传递给原来的func
函数,举例:
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)
现在让我们尝试一个对象方法:
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
绑定至user
,sayHi是“绑定”函数,可以单独调用或传递给setTimeout——没关系,上下文会是正确的。
这里我们看到参数可以正常传递,仅this
通过bind给固定了。
let user = {
firstName: "John",
say(phrase) {
alert(`${phrase}, ${this.firstName}!`);
}
};
let say = user.say.bind(user);
say("Hello"); // Hello, John ("Hello" argument is passed to say)
say("Bye"); // Bye, John ("Bye" is passed to say)
便利的方法:bindAll
如果一个对象有很多方法,且都需要绑定,我们可以通过循环进行绑定:
for (let key in user) {
if (typeof user[key] == 'function') {
user[key] = user[key].bind(user);
}
}
Javascript库也提供了函数用于便捷批量绑定,obj.func.bindAll(obj).
总结
方法func.bind(context,...args)
返回一个函数func的绑定变量,其固定上下文this为第一个给定参数。
通常我们应用绑定去固定对象方法的上下文this,所以我们能放置在其他地方,如在setTimeout
中。在未来的开发中,我们会遇到更多的绑定场景。
本文参考链接:https://blog.csdn.net/neweastsun/article/details/75804130