箭头函数的事件处理程序如何实现上下文绑定?

9
我知道关于"this"绑定的一般理论(函数调用位置很重要,隐式绑定、显式绑定等),以及解决React中"this"绑定问题的方法,例如在构造函数中绑定、箭头函数等,但我在理解其内部机制方面有些困难。
请看下面这两段代码:
class demo extends React.component {
  goToStore(event) {
    console.log(this)
  }

  render() {
    <button onClick={(e) => this.goToStore(e)}>test</button>
  }
}

对比。

class demo extends React.component {
  goToStore(event) {
    console.log(this)
  }

  render() {
    <button onClick={this.goToStore}>test</button>
  }
}

我知道的是:
  • 在这两个版本中,我们最终都会成功进入 goToStore 方法,因为 render() 方法内部的 this 会被 React 自动绑定到组件实例上
  • 第一个版本成功的原因就在于此
  • 第二个版本则失败了,因为 ES6 中的类方法不会被绑定到组件实例上,所以方法内部的 this 会被解析为 undefined

理论上,在第一个版本中会发生以下情况:

  1. 按钮点击处理程序是一个匿名箭头函数,因此每当我在其中引用 this 时,它会从环境中(在本例中为 render())捕获 this。
  2. 然后它调用 goToStore 方法,该方法是常规函数。
  3. 因为这个调用似乎符合隐式绑定的规则(object.function()),所以 object 将成为上下文对象,在这样的函数调用中,它将被用作 this。
  4. 因此,在 goToStore 方法内部,词法捕获的 this(用作上下文对象)将正确地解析为组件实例。

我感觉情况3和4在这里不适用,因为那样的话就适用于第二种情况:

<button onClick={this.goToStore}>test</button>

同时还有this上下文对象。

在这种特定情况下,逐步发生了什么?

3个回答

12

正如MDN文档所述

箭头函数没有自己的this;使用封闭执行上下文的this值

因此,您可以考虑

onClick={(e) => this.goToStore(e)}

可以被写成匿名函数的函数

    (e) => { 
         return this.goToStore(e) 
    }
在这个匿名函数中,this指的是渲染函数的词法上下文,而渲染函数又指的是React类实例。
现在 上下文通常由函数的调用方式确定。当一个函数作为对象的方法被调用时,this会被设置为调用该方法的对象:
var obj = {
    foo: function() {
        return this;   
    }
};

obj.foo() === obj; // true
同样的原则也适用于使用 new 操作符调用函数以创建对象实例。当以这种方式调用函数时,函数内 this 的值将被设置为新创建的实例:
function foo() {
    alert(this);
}

foo() // window
new foo() // foo

当作为非绑定函数调用时,它将默认使用全局上下文或浏览器中的window对象。

因此,由于函数是像this.goToStore()这样被调用的,所以其中的this将指向React组件的上下文。

然而,当您编写onClick={this.goToStore}时,该函数并未执行,而是将其引用分配给onClick函数,之后再调用它,导致this在函数内部被定义为undefined,因为该函数在window对象的上下文中运行。

现在,即使onClick={(e) => this.goToStore(e)}可以工作,但每次调用render时都会创建一个新的函数实例。在您的情况下,可以通过使用箭头函数语法创建goToStore函数来轻松避免这种情况。

goToStore = (e) => {

}

查看文档以获取有关 this 的更多细节。


现在我明白了,所以当点击事件发生时,函数引用会通过一个简单的函数调用被调用,例如 onClick(),导致默认绑定规则,即 window。谢谢! - marchello

2
在第一种情况下,如你所说,上下文是从环境中获取的。 至于第二种情况,您需要记住,在ES6中的类语法只是对更繁琐的原型语法的语法糖,而JavaScript依赖于该语法来实现面向对象编程。
基本上,在第二个例子中,您所做的就像这样:
function demo() {
   // this is the constructor
}
demo.prototype.goToStore = function() {
  // handler
};
demo.prototype.render = function() {
  return React.createElement('button', onClick: this.goToStore);
}

如您所见,onClick属性只是接收函数的引用。每当调用该函数时,不会绑定任何this,并且它将在window对象的上下文中运行。

在老的库中,在现代转译器出现之前,我们经常这样做:

function demo() {
   // this is the constructor     
   this.goToStore = this.goToStore.bind(this);
   // same for every other handler
}
demo.prototype.goToStore = function() {
   // handling code.
};
demo.prototype.render = function() {
  // this goToStore reference has the proper this binded.
  return React.createElement('button', onClick: this.goToStore);
}

现在,所有现代的转译器都会自动处理我提到的最后一个例子。当你在任何类中使用fat arrow方法语法时,Babel基本上会在构造函数中进行自动绑定:

class demo extends Anything {
  constructor() {
  }
  bindedMethod = () => {
    // my context will always be the instance!
  }
}

所有的转换器都会有微小的差异,它们将bindedMethod的定义移到构造函数中,其中this将绑定到运行构造函数的当前实例。


谢谢,现在我明白了。你的回答和Shubham的一样好,如果可以接受两个答案的话,我也会接受它。 - marchello

0

当渲染代码被执行时,this 指的是组件类,因此它能够引用正确的函数。这意味着 element.onClick 指向了 goToStore 方法。

当该函数在浏览器中被调用时,它是从 html 元素的上下文中调用的(即 element.onClick())。因此,除非将 goToStore 方法绑定到组件类的上下文中,否则它将从元素的上下文中继承 this

这个答案 提供了一些更相关的信息和进一步阅读材料。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接