• 欢迎访问天天编码网站,Java技术、技术书单、开发工具,欢迎加入天天编码
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏天天编码吧
  • 我们的淘宝店铺已经开张了哦,传送门:https://shop145764801.taobao.com/

6.10 函数绑定

JS 教程 tiantian 416次浏览 0个评论 扫描二维码

当使用setTimeout结合使用对象方法时,或者传递对象方法时,存在一个著名的问题:”丢失this“。

忽然,this变得无法正常工作。对于 JavaScript 的新手而言,这是一个典型的场景,但是很多资深开发者也会遭遇此问题。

丢失this

我们已经知道,在 JavaScript中很容易丢失this。一旦某个方法被传递,与其对象进行了分离,那么其this就会被丢失。

下面使用setTimeout时丢失this的常见场景:

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

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

正如我们所见,那个输出并不是this.firstName对应的"John",而是undefined

这是因为setTimeout获得的函数user.sayHi,已经与对象相分离。最后一行代码等效于如下所示:

let f = user.sayHi;
setTimeout(f, 1000); // lost user context

浏览器中的setTimeout方法具有一些特殊性:对于此函数调用,它会设置this=window(对于 Node.JS而言,this的值来源于定时器对象,此处无关紧要)。所以,this.firstName被用来尝试获取window.firstName,而相应的值并不存在。在其他类似的场景中,this会仅仅演变为undefined

这个任务非常地典型——我们希望传递对象方法到某处,并且在该处被调用。如何确保该函数可以在正确的上下文中被调用?

解决方案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触发之前(存在1秒的延迟),user修改了其值?那么,忽然地,它就会调用错误的对象。

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)的结果是一个特殊的类函数“特殊对象”,它可以像函数一样被调用,而且透明地传输调用给func,并且设置this=context

换句话说,调用boundFunc就像调用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方法,两种情况下他们的上下文都是正确的。

在此处我们可以看到,所有的参数都是原样传递,除了thisbind固定:

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 库同样提供了函数来转换大量的绑定工作,比如,lodash的_.bindAll(obj)。

总结

方法func.bind(context, ...args)返回一个函数func的“绑定变种”,它固定this上下文为第一个给定的参数context

一般地,我们应用bind在对象方法来修正this,所以我们可以将它们进行传递。举例,传递给setTimeout。在现在的开发过程中,有很多理由需要使用bind方法,我们将很快遇到它们。

任务


绑定函数为方法

下列示例代码的输出是什么?

function f() {
  alert( this ); // ?
}

let user = {
  g: f.bind(null)
};

user.g();

二次绑定

我们可以通过额外的绑定来修改this吗?

下面代码的输出结果?

function f() {
  alert(this.name);
}

f = f.bind( {name: "John"} ).bind( {name: "Ann" } );

f();

绑定后的函数属性

假设函数的属性包括一个值,该值会在绑定后发生变化吗?为什么?

function sayHi() {
  alert( this.name );
}
sayHi.test = 5;

let bound = sayHi.bind({
  name: "John"
});

alert( bound.test ); // what will be the output? why?

寻找遗失的this

下列代码中的调用askPassword()应该检查那个 password,然后基于答案调用那个user.loginOk/loginFail

但是此代码会导致一个错误,为什么?

修正下面代码的最后一行来使得代码工作正确。

function askPassword(ok, fail) {
  let password = prompt("Password?", '');
  if (password == "rockstar") ok();
  else fail();
}

let user = {
  name: 'John',

  loginOk() {
    alert(`${this.name} logged in`);
  },

  loginFail() {
    alert(`${this.name} failed to log in`);
  },

};

askPassword(user.loginOk, user.loginFail);


天天编码 , 版权所有丨本文标题:6.10 函数绑定
转载请保留页面地址:http://www.tiantianbianma.com/function-bind.html/
喜欢 (1)
支付宝[多谢打赏]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址