做者:Dmitri Pavlutin翻译:疯狂的技术宅javascript
原文:https://dmitripavlutin.com/fi...前端
未经容许严禁转载java
我喜欢 JavaScript 中可以更改函数执行上下文(也称为 this
)的特性。git
例如,你能够在相似数组的对象上使用数组方法:程序员
const reduce = Array.prototype.reduce; function sumArgs() { return reduce.call(arguments, (sum, value) => { return sum += value; }); } sumArgs(1, 2, 3); // => 6
可是从另外一方面来讲,this
关键字很难掌握。github
你可能会常常去检查 this
的值不正确的缘由。如下各节将会教给你一些把 this
绑定到所需的值简单的方法。面试
在开始以前,我须要一个辅助函数 execute(func)
。它只是用来执行做为参数的函数:segmentfault
function execute(func) { return func(); } execute(function() { return 10 }); // => 10
如今,让咱们继续了解围绕 this
的错误的本质:方法分离。数组
Person
类包含字段 firstName
和 lastName
。另外,它还有 getFullName()
方法,返回全名。浏览器
Person
的一种可能的实现方式是:
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('John', 'Smith'); agent.getFullName(); // => 'John Smith'
你会看到 Person
函数做为构造函数被调用:new Person('John','Smith')
。在 Person
函数内部建立新的实例。
agent.getFullName()
返回 person
的全名:'John Smith'
。不出所料,getFullName()
方法中的 this
等同于 agent
。
若是帮助函数执行 help.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' // is equivalent to: const getFullNameSeparated = agent.getFullName; execute(getFullNameSeparated); // => 'undefined undefined'
这种效果就是我所说的与对象分离的方法。当方法被分离并随后执行时,它与其原始对象没有任何关系。
为了确保方法中的 this
指向正确的对象,你必须:
agent.getFullName()
this
静态绑定到包含的对象(使用箭头函数,.bind()
方法等)在方法分离问题中,返回的 this
不正确,如下面不一样的形式出现:
// `this` inside `methodHandler()` is the global object setTimeout(object.handlerMethod, 1000);
// React: `this` inside `methodHandler()` is the global object <button onClick={object.handlerMethod}> Click me </button>
让咱们继续了解一些有用的方法,来解决即便方法与对象是分开的,也能使其始终指向所需对象的问题。
使 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('John', 'Smith'); agent.getFullName(); // => 'John Smith' execute(agent.getFullName); // => 'John Smith'
getFullName()
会静态关闭 self
变量,从而有效地手动绑定到 this
。
如今,当调用 execute(agent.getFullName)
时返回 'John Smith'
,由于 getFullName()
方法始终具备正确的 this
值,因此可以正常工做。
有没有一种能够在没有其余变量的状况下静态绑定 this
的方法?是的,这正是箭头函数的做用。
为了使用箭头函数,让咱们重构 Person
:
function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; this.getFullName = () => `${this.firstName} ${this.lastName}`; } const agent = new Person('John', 'Smith'); agent.getFullName(); // => 'John Smith' execute(agent.getFullName); // => 'John Smith'
箭头函数用词法绑定 this
。简而言之,它使用定义在其中的外部函数的 this
值。
我建议在全部须要使用外部函数上下文的状况下都使用箭头函数。
让咱们再向前迈出一步,并使用 ES2015 类来重构 Person
。
class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } getFullName() { return `${this.firstName} ${this.lastName}`; } } const agent = new Person('John', 'Smith'); agent.getFullName(); // => 'John Smith' 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('John', 'Smith'); agent.getFullName(); // => 'John Smith' execute(agent.getFullName); // => 'John Smith'
构造函数中的 this.getFullName = this.getFullName.bind(this)
把 getFullName()
方法绑定到类实例。
execute(agent.getFullName)
能够正常工做,返回 'John Smith'
。
上述使用手动上下文绑定的方法须要样板代码。幸运的是,仍有改进的空间。
你能够用 JavaScript 的类字段建议来定义胖箭头方法:
class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } getFullName = () => { return `${this.firstName} ${this.lastName}`; } } const agent = new Person('John', 'Smith'); agent.getFullName(); // => 'John Smith' execute(agent.getFullName); // => 'John Smith'
胖箭头函数 getFullName = () => { ... }
已绑定到类实例,即便你将方法与其对象分离开也是如此。
这是在类中绑定 this
的最有效,最简洁的方法。
与对象分离的方法对 this
产生了许多误解。你应该意识到这种影响。
要静态绑定 this
,你能够手动使用一个附加变量 self
来保存正确的上下文对象。可是更好的选择是使用箭头函数,它天生被设计为按词法绑定 this
。
在类中,你可使用 bind()
方法在构造函数内部手动绑定类方法。
若是你想跳过编写样板代码,那么新的 JavaScript 建议类字段会带来胖箭头方法,该方法会自动将 this
绑定到类实例。