当将对象方法作为回调进行传递,例如传递给 setTimeout,这儿会存在一个常见的问题:“丢失 this”。
例如

  1. let user = {
  2. firstName: "John",
  3. sayHi() {
  4. alert(`Hello, ${this.firstName}!`);
  5. }
  6. };
  7. 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

解决方案一:包装器

最简单的解决方案是使用一个包装函数:

  1. let user = {
  2. firstName: "John",
  3. sayHi() {
  4. alert(`Hello, ${this.firstName}!`);
  5. }
  6. };
  7. setTimeout(function() {
  8. user.sayHi(); // Hello, John!
  9. }, 1000);

现在它可以正常工作了,因为它从外部词法环境中获取到了 user,就可以正常地调用方法了。

相同的功能,但是更简短:

  1. setTimeout(() => user.sayHi(), 1000); // Hello, John!

所有的函数在“诞生”时都会记住创建它们的词法环境
使用包装器,创建了一个新的函数

使用包装器的缺点是如果user对象被重新赋值,函数调用也跟着改变

解决方案二:bind

  1. let user = {
  2. firstName: "John",
  3. sayHi() {
  4. alert(`Hello, ${this.firstName}!`);
  5. }
  6. };
  7. let sayHi = user.sayHi.bind(user);
  8. sayHi(); // Hello, John!
  9. setTimeout(sayHi, 1000); // Hello, John!
  10. user = {
  11. sayHi() { alert("Another user in setTimeout!"); }
  12. };

bind创建了一个新的函数,即时user对象里的sayHi函数被改变了也没关系

解决方案三:使用类字段制作绑定方法

  1. class User {
  2. constructor(name){
  3. this.firstName = name
  4. }
  5. sayHi = () => {
  6. alert(`Hello, ${this.firstName}!`)
  7. }
  8. }
  9. let user = new User('John')
  10. setTimeout(user.sayHi, 1000)

使用类字段创建的方法,this总是指向其对象
此方法在React事件监听时常用