尽管之前的答案已经提供了解决方案的基本概述(即绑定、箭头函数、为您执行此操作的装饰器),但我还没有找到真正解释为什么这是必要的答案——在我看来这是根本混乱,并导致不必要的步骤,例如不必要的重新绑定和盲目跟随他人的做法。
this
是动态的
要了解这种特定情况,请简要介绍其this
工作原理。这里的关键是这this
是一个运行时绑定,取决于当前的执行上下文。因此,为什么它通常被称为“上下文”——提供有关当前执行上下文的信息,以及为什么需要绑定是因为你失去了“上下文”。但是让我用一个片段来说明这个问题:
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!
可能会意外地发现它记录了 undefined ——3
去哪儿了?答案在于“上下文”,或者你如何执行一个函数。比较我们如何调用函数:
// 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有关
这是一个遇到问题的 React 组件(为简洁起见而缩短)的示例this
:
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如何处理事件处理程序:
// 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
最终会分配给变量listener
3。但是现在你看到问题出现了——因为我们已经分配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 工作:
handleClick = () => { /* Can use this just fine here */ }
变成:
constructor() {
super();
this.handleClick = () => {}
}
这是因为箭头函数不绑定它们自己的 this ,而是占用this
它们封闭范围的the 。在这种情况下,指向 React 组件实例的constructor
's this
,从而为您提供正确的this
. 4
1我使用“方法”来指代应该绑定到对象的函数,而“函数”则指代那些没有绑定到对象的函数。
2在第二个片段中,记录 undefined 而不是 3,因为当无法通过特定上下文确定时,this
默认为全局执行上下文(window
如果不是严格模式,否则为 else undefined
)。并且在示例window.foo
中不存在因此产生未定义。
3如果您深入了解事件队列中的事件是如何执行的,invokeGuardedCallback
则在侦听器上调用。
4它实际上要复杂得多。React 内部尝试将Function#apply
侦听器用于自己的用途,但这不起作用箭头函数,因为它们根本不绑定this
。这意味着,当this
实际评估箭头函数时,this
会解析module当前代码的每个执行上下文的每个词法环境。最终解析为具有this
绑定的执行上下文是构造函数,它有一个this
指向当前 React 组件实例的指针,允许它工作。