【问题标题】:ES6 functions, arrow functions and 'this' in an ES6 class [duplicate]ES6 类中的 ES6 函数、箭头函数和“this”[重复]
【发布时间】:2018-08-01 20:58:16
【问题描述】:
class App extends Component {
  constructor(props) {
    ...
  }

  onChange = (e) => this.setState({term: e.target.value})

  onSubmit(e){
    e.preventDefault();
    const api_key = "C1hha1quJAQZf2JUlK";
    const url = `http://api.giphy.com/v1/gifs/search?q=${this.state.term}&api_key=${api_key}`;
  }

  render() {
    return (
      <div>
        <form onSubmit={this.onSubmit}>
          <input value={this.state.term} onChange={this.onChange}/>
          <button>Search!</button>
        </form>
      </div>
    );
  }
}

类中声明的两种函数有什么区别(onChange和onSubmit)。如果我将 const url 中的 this.sate 声明为 ES6 类方法,但将其更改为箭头函数可以修复它,则会出现错误。

我想知道在这两种情况下究竟是如何处理“this”的

另外,我该怎么做呢?比如说,如果我想使用相同的 onSubmit 函数(ES6 类方法)但想在调用它时(在表单元素中)处理它,我该怎么做?

使用 this.onSubmit.bind(this) ?

【问题讨论】:

    标签: javascript reactjs ecmascript-6 es6-class arrow-functions


    【解决方案1】:

    了解以下语法很重要:

    class A {
      method = () => {}
    }
    

    只是在类构造函数中创建实例方法的语法糖:

    class A {
      constructor() {
        this.method = () => {}
      }
    }
    

    注意:此语法还不是 JavaScript 语言的官方部分 (currently in stage 3),因此您必须使用 transpiler like Babel to handle it

    methodthis 的值是A 类,因为这是this 在构造函数中指向的内容(因为arrow functions 从定义它们的范围继承上下文):

    class A {
      constructor() {
        this.method = () => this;
      }
    }
    
    const instance = new A();
    console.log(instance.method() === instance); // true

    在类上定义一个常规(非箭头函数)方法会在类原型(不是实例)上创建一个方法,但没有设置 this 将是什么规则(因为 this 在 JS 中是动态的,@987654324 @)。

    class A {
      method() {}
    }
    
    console.log(new A().method === A.prototype.method); // true

    如果在类实例上调用以这两种方式中的任何一种定义的方法(通过.),根据当函数作为对象的方法调用时this 的绑定规则,@987654341 @ 在这两种情况下都会指向类实例:

    class A {
      constructor() {
        this.methodOnInstance = () => this;
      }
      methodOnPrototype() { return this; }
    }
    
    const instance = new A();
    console.log(
      instance.methodOnInstance() === instance.methodOnPrototype(), // true
      instance.methodOnPrototype() === instance // true
    );

    上面两个方法声明的一个主要区别是实例方法有thisalways固定到类实例,而类(原型)方法没有(我们可以通过使用Function.prototype.applyFunction.prototype.call)

    class A {
      constructor() {
        this.methodOnInstance = () => this;
      }
      methodOnPrototype() { return this; }
    }
    
    const instance = new A();
    console.log(
      instance.methodOnInstance() === instance.methodOnPrototype(), // true
      instance.methodOnPrototype.call('new this') === 'new this' // true
    );

    this 更改的常见情况是在事件处理程序中,其中事件处理程序调用传递给它的函数并将上下文绑定到发生事件的元素(因此将 this 的值覆盖为是被点击的元素或任何事件)

    这在 React 中也发生在所有 (synthetic) DOM 事件处理程序中。

    因此,如果我们希望方法的上下文始终指向 React 组件的实例,我们可以使用实例方法。

    另一种限制上下文但不使用需要 Babel 的特殊实例方法语法的方法是,通过使用绑定上下文的类(原型)方法创建一个新函数(使用Function.prototype.bind),直接自己创建一个实例方法:

    class A {
      constructor() {
        this.methodOnInstance = this.methodOnPrototype.bind(this);
      }
      methodOnPrototype() { return this; }
    }
    
    const instance = new A();
    console.log(
      instance.methodOnInstance() === instance.methodOnPrototype(), // true
      instance.methodOnPrototype() === instance // true
    );

    这使我们能够获得与使用特殊实例方法语法相同的结果,但使用当前可用的工具(ES2017 及以下)。

    如果出于某种原因我们想要一个始终绑定到非类实例的方法,我们也可以这样做:

    class A {
      constructor() {
        this.method = this.method.bind(console);
      }
      method() { return this; }
    }
    
    const instance = new A();
    console.log(
      instance.method() === console // true
    );

    【讨论】:

      【解决方案2】:

      关键区别在于,在 ES5 中我们没有自动绑定,这意味着您必须手动绑定您的事件处理函数,以便在函数中使用 state 或 props 进行反应。但是在 ES6 中它会自动绑定。这是关键的区别

      ES5:你必须最好在构造函数中绑定 onSubmit

      //is valid
      this.onSubmit = this.onSubmit.bind(this);
      
      onSubmit(e){
          e.preventDefault();
          const api_key = "C1hha1quJAQZf2JUlK";
          const url = `http://api.giphy.com/v1/gifs/search?q=${this.state.term}&api_key=${api_key}`;
        }
      

      ES6:

      以下是有效的,因为它会自动绑定。

      onChange = (e) => this.setState({term: e.target.value})
      

      【讨论】:

      • 不,它不做自动绑定。它根本没有把函数放在原型上。
      • 对。我理解术语自动绑定:)
      • 绑定意味着在泛型(动态-this)方法上使用bind
      【解决方案3】:

      您需要在类的构造函数中使用 ES6 类方法中的 bind。本质上,箭头函数会自动为您执行此操作。

      constructor(props) {
        super(props);
      
        this.onSubmit = this.onSubmit.bind(this);
      }
      

      这里要注意的更重要的一点是,我相信这里的箭头函数将在类的每个实例上创建,其中 ES6 类方法将成为类原型的一部分并在所有实例之间共享。

      【讨论】:

        【解决方案4】:

        箭头函数表达式的语法比函数短 表达式并且没有自己的 this、arguments、super 或 新目标。这些函数表达式最适合非方法 函数,它们不能用作构造函数。

        箭头函数在词法上绑定它们的上下文,因此 this 实际上是指原始上下文。

        在 ES3/4 函数声明中,您可以通过存储在其他变量中来使用 this

        const that = this;
        onSubmit(e){
            e.preventDefault();
            const api_key = "***************";
            const url = `http://api.giphy.com/v1/gifs/search?q=${that.state.term}&api_key=${api_key}`;
          }
        

        【讨论】:

          【解决方案5】:

          另外,我该怎么做呢?比如说,如果我想使用相同的 onSubmit 函数(ES6 类方法)但想在调用它时(在表单元素中)处理它,我该怎么做?

          使用 this.onSubmit.bind(this) ?

          是的,您必须将方法绑定到构造函数中的组件。这是因为箭头函数会自动绑定到类,因此 this 的范围是在方法中设置的。而onSubmit 是一个尚未绑定的常规函数​​,因此方法内的 this 将引用该函数而不是组件。

          【讨论】: