虽然前面的答案提供了解决方案的基本概述(即绑定、箭头函数、为您执行此操作的装饰器),但我还没有遇到真正解释为什么的答案是必要的——在我看来,这是混乱的根源,并导致不必要的步骤,例如不必要的重新绑定和盲目跟随别人的做法。
this 是动态的
为了了解这个具体情况,简单介绍一下this是如何工作的。这里的关键是this 是一个运行时绑定,并且依赖于当前的执行上下文。因此为什么它通常被称为“上下文”——提供有关当前执行上下文的信息,以及为什么需要绑定是因为你失去了“上下文”。但是让我用一个 sn-p 来说明这个问题:
const foobar = {
bar: function () {
return this.foo;
},
foo: 3,
};
console.log(foobar.bar()); // 3, all is good!
在这个例子中,我们得到了3,正如预期的那样。但是举个例子:
const barFunc = foobar.bar;
console.log(barFunc()); // Uh oh, undefined!
发现它记录未定义可能是出乎意料的——3 去哪儿了?答案在于“context”,或者你如何执行一个函数。比较我们如何调用函数:
// Example 1
foobar.bar();
// Example 2
const barFunc = foobar.bar;
barFunc();
注意区别。在第一个示例中,我们准确地指定了 bar 方法1 的位置——在 foobar 对象上:
foobar.bar();
^^^^^^
但是在第二种情况下,我们将方法存储到一个新变量中,并使用该变量调用该方法,而没有明确说明该方法实际存在的位置,因此失去了上下文:
barFunc(); // Which object is this function coming from?
问题就在这里,当您将方法存储在变量中时,有关该方法所在位置的原始信息(执行该方法的上下文)会丢失。如果没有这些信息,在运行时,JavaScript 解释器就无法绑定正确的this——没有特定上下文,this 无法按预期工作2。
与 React 相关
这是一个遇到 this 问题的 React 组件(为简洁起见)的示例:
handleClick() {
this.setState(({ clicks }) => ({ // setState is async, use callback to access previous state
clicks: clicks + 1, // increase by 1
}));
}
render() {
return (
<button onClick={this.handleClick}>{this.state.clicks}</button>
);
}
但是为什么,以及上一节与此有何关系?这是因为它们遭受相同问题的抽象。如果你看看如何React handles event handlers:
// Edited to fit answer, React performs other checks internally
// props is the current React component's props, registrationName is the name of the event handle prop, i.e "onClick"
let listener = props[registrationName];
// Later, listener is called
因此,当您执行onClick={this.handleClick} 时,方法this.handleClick 最终会分配给变量listener3。但是现在您看到问题出现了——因为我们已经将this.handleClick 分配给listener,我们不再指定handleClick 的确切来源!从 React 的角度来看,listener 只是一些函数,没有附加到任何对象(或者在这种情况下,是 React 组件实例)。我们失去了上下文,因此解释器无法推断出 this 值以使用 inside handleClick。
为什么绑定有效
您可能想知道,如果解释器在运行时决定 this 值,为什么我可以绑定处理程序以使其工作?这是因为您可以在运行时使用Function#bind 来保证this 值。这是通过在函数上设置内部this 绑定属性来完成的,允许它不推断this:
this.handleClick = this.handleClick.bind(this);
当这一行被执行时,大概在构造函数中,当前的this被捕获(React组件实例)并设置为一个全新函数的内部this绑定,返回来自Function#bind。这样可以确保在运行时计算 this 时,解释器不会尝试推断任何内容,而是使用您提供的 this 值。
为什么箭头函数属性起作用
箭头函数类属性目前通过Babel基于transpilation工作:
handleClick = () => { /* Can use this just fine here */ }
变成:
constructor() {
super();
this.handleClick = () => {}
}
这之所以有效,是因为箭头函数不绑定自己的 this,而是采用其封闭范围的 this。在本例中,constructor 的 this 指向 React 组件实例,从而为您提供正确的 this。4
1 我使用“方法”来指代应该绑定到对象的函数,而使用“函数”来指代那些没有绑定的函数。
2 在第二个 sn-p 中,记录 undefined 而不是 3,因为 this 默认为全局执行上下文(window 未处于严格模式时,否则 undefined)当无法通过特定上下文确定时。在示例中,window.foo 不存在,因此产生未定义。
3 如果您深入了解事件队列中的事件是如何执行的,则在侦听器上调用invokeGuardedCallback。
4 实际上要复杂得多。 React 在内部尝试在侦听器上使用 Function#apply 以供自己使用,但这不起作用箭头函数,因为它们根本不绑定 this。这意味着,当实际评估箭头函数内的this 时,this 将解析模块当前代码的每个执行上下文的每个词法环境。最终解析为具有this 绑定的执行上下文是构造函数,它有一个指向当前 React 组件实例的this,允许它工作。