前端入门学习笔记(二十八)JavaScript入门(十一)this丢失怎么办?——函数绑定

函数绑定

和对象方法或者和传递对象方法一起使用 setTimeout 时,有一个很常见的问题:“this 丢失”。

突然,this 就停止正常运作了。这种情况在开发的初学者中很典型,但有时也会出现在有经验开发者的代码中。


丢失 “this”

我们已经知道,在 JavaScript 中,this 很容易就会丢失。一旦一个方法被传递到另一个与对象分离的地方 —— this 就丢失了。

下面是使用 setTimeout 时 this 时如何丢失的:

let user = {
  firstName: "John",
  sayHi() {
    console.log(`Hello, ${this.firstName}!`);
  }
};

setTimeout(user.sayHi, 1000); // Hello, undefined!

正如我们看到的那样,this.firstName 不是输出为 “John”,而是 undefined!

这是因为 setTimeout 获取到了函数 user.sayHi,但它和对象分离开了。最后一行可以写为:

let f = user.sayHi;
setTimeout(f, 1000); // 用户上下文丢失

浏览器中的方法 setTimeout 有些特殊:它为函数的调用设定了 this=window(对于 Node.JS,this 则会成为时间对象,但其实 this 到底变成什么并不十分重要)。所以对于 this.firstName 它其实试图获取的是 window.firstName,这个变量并不存在。在其他一些类似的情况下,通常 this 就会成为 undefined。

这个需求很典型——我们希望将一个对象的方法传递到别的地方(这里——是为了调度程序)然后调用。如何确保它将会在正确的上下文中被调用呢?


解决方法:bind

原文中有两种方案,一种是使用一个包装函数,但在变量临时改变的时,仍会出问题。
所以此处仅展示第二种解决方案bind
函数对象提供了一个内建方法 bind,它可以固定住 this。

基本的语法是:

// 稍后将会有更复杂的语法
let boundFunc = func.bind(context);

func.bind(context) 的结果是一个特殊的像函数一样的“外来对象”,它可以像函数一样被调用并且透明的将调用传递给 func 并设置 this=context。

换句话说,调用 boundFunc 就像是调用 func 并且固定住了 this。

举个例子,这里 funcUser 将调用传递给了 func 同时 this=user:

let user = {
  firstName: "John"
};

function func() {
  console.log(this.firstName);
}

let funcUser = func.bind(user);
funcUser(); // John

这里 func.bind(user) 作为 func 的“边界变量”,同时固定了 this=user。

所有的参数都会被传递给初始的 func,就像本来就是调用了它一样,例如:

let user = {
  firstName: "John"
};

function func(phrase) {
  console.log(phrase + ', ' + this.firstName);
}

// 将 this 绑定给 user
let funcUser = func.bind(user);

funcUser("Hello"); // Hello, John(参数 "Hello" 被传递了,并且 this=user)

下面我们来尝试一个对象的方法:

let user = {
  firstName: "John",
  sayHi() {
    console.log(`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) {
    console.log(`${phrase}, ${this.firstName}!`);
  }
};

let say = user.say.bind(user);

say("Hello"); // Hello, John ("Hello" 参数被传递给了函数 say)
say("Bye"); // Bye, John ("Bye" 被传递给了函数 say)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章