虽然前面的回答已经提供了解决方案的基本概述(即绑定、箭头函数、为您做这件事的装饰器),但我还没有遇到一个真正解释为什么这是必要的答案——在我看来,这是困惑的根源,并导致不必要的步骤,如不必要的重新绑定和盲目地跟随别人做什么。
这是动态的
要了解这种具体情况,简要介绍一下它是如何工作的。这里的关键是,这是一个运行时绑定,依赖于当前的执行上下文。因此,为什么它通常被称为“上下文”——提供当前执行上下文的信息,以及为什么需要绑定是因为您失去了“上下文”。但让我用一个片段来说明这个问题:
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去哪里了?答案在于“上下文”,或者你如何执行一个函数。比较一下我们如何调用函数:
// Example 1
foobar.bar();
// Example 2
const barFunc = foobar.bar;
barFunc();
注意区别。在第一个例子中,我们精确地指定了bar method1在foobar对象上的位置:
foobar.bar();
^^^^^^
但在第二种方法中,我们将方法存储到一个新变量中,并使用该变量来调用该方法,而不显式地声明该方法实际存在的位置,从而丢失上下文:
barFunc(); // Which object is this function coming from?
问题就在这里,当你在变量中存储一个方法时,关于该方法所在位置的原始信息(该方法正在执行的上下文)就丢失了。在运行时,如果没有这些信息,JavaScript解释器就无法绑定正确的this——如果没有特定的上下文,这将无法正常工作2。
关于React
下面是一个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如何处理事件处理器:
// 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。但现在你看到问题了,因为我们分配了这个。handleClick到监听器,我们不再指定handleClick来自哪里!从React的角度来看,监听器只是一个函数,没有附加到任何对象(或者在这种情况下,React组件实例)。我们丢失了上下文,因此解释器无法推断出在handleClick内部使用的this值。
为什么绑定有效
您可能想知道,如果解释器在运行时决定this值,为什么我可以绑定处理程序,以便它工作?这是因为你可以使用Function#bind在运行时保证This值。这是通过在函数上设置一个内部This绑定属性来实现的,允许它不推断这个:
this.handleClick = this.handleClick.bind(this);
当执行这一行时,假设在构造函数中,当前的this被捕获(React组件实例),并设置为一个全新函数的内部this绑定,从function #bind返回。这可以确保在运行时计算这个值时,解释器不会尝试推断任何东西,而是使用您提供的This值。
为什么箭头函数属性有效
箭头函数类属性目前通过Babel基于翻译工作:
handleClick = () => { /* Can use this just fine here */ }
就变成:
constructor() {
super();
this.handleClick = () => {}
}
这是因为箭头函数不绑定自己的this,而是取其封闭范围的this。在本例中,构造函数是this,它指向React组件实例——因此给出了正确的this.4
我用“method”来指代应该绑定到对象上的函数,用“function”来指代那些没有绑定到对象上的函数。
2在第二个代码片段中,未定义被记录为日志,而不是3,因为当不能通过特定上下文确定时,默认为全局执行上下文(非严格模式时为窗口,否则为未定义)。在示例窗口中。Foo不存在,因此产生undefined。
如果你深入到事件队列中事件是如何执行的,监听器上调用invokeGuardedCallback。
4 It's actually a lot more complicated. React internally tries to use Function#apply on listeners for its own use, but this does not work arrow functions as they simply do not bind this. That means, when this inside the arrow function is actually evaluated, the this is resolved up each lexical environment of each execution context of the current code of the module. The execution context which finally resolves to have a this binding is the constructor, which has a this pointing to the current React component instance, allowing it to work.