Javascript函数绑定

来源:互联网 发布:spark sql 数据仓库 编辑:程序博客网 时间:2024/06/10 06:57

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 seconduser = { sayHi() { alert("Another user in setTimeout!"); } };// Another user in setTimeout?!?

下一个解决方案保证这样的事情不会发生。

方案2:绑定

函数提供了一个内置方法bind,允许我们修改this。基本的语法为:

// more complex syntax will be little laterlet 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 userlet 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中。在未来的开发中,我们会遇到更多的绑定场景。