共计 4239 个字符,预计需要花费 11 分钟才能阅读完成。
作者:Dmitri Pavlutin
来源:dmitripavlutin
译者:前端小智
为了保证的可读性,本文采用意译而非直译。
想阅读更多优质文章请猛戳 GitHub 博客, 一年百来篇优质文章等着你!
我喜欢在 JS 中更改函数执行上下文的指向,也称为 this
指向。
例如,咱们可以在类数组对象上使用数组方法:
const reduce = Array.prototype.reduce;
function sumArgs() {return reduce.call(arguments, (sum, value) => {return sum += value;});
}
sumArgs(1, 2, 3); // => 6
另一方面,this
很难把握。
咱们经常会发现自己用的 this
指向不正确。下面的教你如何简单地将 this
绑定到所需的值。
在开始之前,我需要一个辅助函数execute(func)
,它仅执行作为参数提供的函数。
function execute(func) {return func();
}
execute(function() {return 10}); // => 10
现在,继续理解围绕 this
错误的本质: 方法分离。
1. 方法分离问题
假设有一个类 Person
包含字段 firstName
和lastName
。此外,它还有一个方法getFullName()
,该方法返回此人的全名。如下所示:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.getFullName = function() {
this === agent; // => true
return `${this.firstName} ${this.lastName}`;
}
}
const agent = new Person('前端', '小智');
agent.getFullName(); // => '前端 小智'
可以看到 Person
函数作为构造函数被调用:new Person('前端', '小智')
。函数内部的 this
表示新创建的实例。
getfullname()
返回此人的全名:'前端 小智'
。正如预期的那样,getFullName()
方法内的 this
等于agent
。
如果辅助函数执行 agent.getFullName
方法会发生什么:
execute(agent.getFullName); // => 'undefined undefined'
执行结果不正确:'undefined undefined'
,这是 this
指向不正确导致的问题。
现在在 getFullName()
方法中,this
的值是全局对象(浏览器环境中的 window
)。this
等于 window
,${window.firstName} ${window.lastName}
执行结果是 'undefined undefined'
。
发生这种情况是因为在调用 execute(agent.getFullName)
时该方法与对象分离。基本上发生的只是常规函数调用(不是方法调用):
execute(agent.getFullName); // => 'undefined undefined'
// 等价于:
const getFullNameSeparated = agent.getFullName;
execute(getFullNameSeparated); // => 'undefined undefined'
这个就是所谓的方法从它的对象中分离出来,当方法被分离,然后执行时,this
与原始对象没有连接。
为了确保方法内部的 this
指向正确的对象,必须这样做
- 以属性访问器的形式执行方法:
agent.getFullName()
- 或者静态地将
this
绑定到包含的对象 (使用箭头函数、.bind()
方法等)
方法分离问题,以及由此导致 this
指向不正确,一般会在下面的几种情况中出现:
回调
// `methodHandler()` 中的 `this` 是全局对象
setTimeout(object.handlerMethod, 1000);
在设置事件处理程序时
// React: `methodHandler()` 中的 `this` 是全局对象
<button onClick={object.handlerMethod}>
Click me
</button>
接着介绍一些有用的方法,即如果方法与对象分离,如何使 this
指向所需的对象。
2. 关闭上下文
保持 this
指向类实例的最简单方法是使用一个额外的变量self
:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
const self = this;
this.getFullName = function() {
self === agent; // => true
return `${self.firstName} ${self.lastName}`;
}
}
const agent = new Person('前端', '小智');
agent.getFullName(); // => '前端 小智'
execute(agent.getFullName); // => '前端 小智'
getFullName()
静态地关闭 self
变量,有效地对 this
进行手动绑定。
现在,当调用 execute(agent.getFullName)
时,一切工作正常,因为 getFullName()
方法内 this
总是指向正确的值。
3. 使用箭头函数
有没有办法在没有附加变量的情况下静态绑定this
?是的,这正是箭头函数的作用。
使用箭头函数重构Person
:
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.getFullName = () => `${this.firstName} ${this.lastName}`;
}
const agent = new Person('前端', '小智');
agent.getFullName(); // => '前端 小智'
execute(agent.getFullName); // => '前端 小智'
箭头函数以词法方式绑定 this
。简单来说,它使用来自其定义的外部函数this
的值。
建议在需要使用外部函数上下文的所有情况下都使用箭头函数。
4. 绑定上下文
现在让咱们更进一步,使用 ES6 中的类重构Person
。
class Person {constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName() {return `${this.firstName} ${this.lastName}`;
}
}
const agent = new Person('前端', '小智');
agent.getFullName(); // => '前端 小智'
execute(agent.getFullName); // => 'undefined undefined'
不幸的是,即使使用新的类语法,execute(agent.getFullName)
仍然返回“undefined undefined”
。
在类的情况下,使用附加的变量 self
或箭头函数来修复 this
的指向是行不通的。
但是有一个涉及 bind()
方法的技巧,它将方法的上下文绑定到构造函数中:
class Person {constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
this.getFullName = this.getFullName.bind(this);
}
getFullName() {return `${this.firstName} ${this.lastName}`;
}
}
const agent = new Person('前端', '小智');
agent.getFullName(); // => '前端 小智'
execute(agent.getFullName); // => '前端 小智'
构造函数中的 this.getFullName = this.getFullName.bind(this)
将方法 getFullName()
绑定到类实例。
execute(agent.getFullName)
按预期工作,返回'前端 小智'
。
5. 胖箭头方法
bind
方式有点太过冗长,咱们可以使用胖箭头的方式:
class Person {constructor(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
getFullName = () => {return `${this.firstName} ${this.lastName}`;
}
}
const agent = new Person('前端', '小智');
agent.getFullName(); // => '前端 小智'
execute(agent.getFullName); // => '前端 小智'
胖箭头方法 getFullName =() =>{…}
绑定到类实例,即使将方法与其对象分离。
这种方法是在类中绑定 this
的最有效和最简洁的方法。
6. 总结
与对象分离的方法会产生 this 指向不正确问题。静态地绑定 this
,可以手动使用一个附加变量self
来保存正确的上下文对象。然而,更好的替代方法是使用箭头函数,其本质上是为了在词法上绑定this
。
在类中,可以使 用 bind()
方法手动绑定构造函数中的类方法。当然如果你不用使用 bind
这种冗长方式,也可以使用简洁方便的胖箭头表示方法。
原文:https://github.com/valentinog…
代码部署后可能存在的 BUG 没法实时知道,事后为了解决这些 BUG,花了大量的时间进行 log 调试,这边顺便给大家推荐一个好用的 BUG 监控工具 Fundebug。
交流
干货系列文章汇总如下,觉得不错点个 Star,欢迎 加群 互相学习。
https://github.com/qq44924588…
我是小智,公众号「大迁世界」作者,对前端技术保持学习爱好者。我会经常分享自己所学所看的干货,在进阶的路上,共勉!
关注公众号,后台回复 福利,即可看到福利,你懂的。