Skip to main content
 首页 » 编程设计

Javascript函数绑定

2022年07月19日139shanyou

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