Javascript函数绑定

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中。在未来的开发中,我们会遇到更多的绑定场景。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值