ES6 functions, arrow functions and 'this'

2019-08-27 11:00发布

This question already has an answer here:

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>
    );
  }
}

What is the difference between the two type of functions declared in the class (onChange and onSubmit). I get an error on referencing this.sate in const url if I declare it as an ES6 class method but changing it to arrow function fixes it.

I want to know how exactly 'this' is handled in both the cases

Also, how do I do it the other way? Say, if I want to use the same onSubmit function (ES6 class method) but want to handle this when I call it (in the form element), how do I do it ?

Using this.onSubmit.bind(this) ?

5条回答
成全新的幸福
2楼-- · 2019-08-27 11:21

An arrow function expression has a shorter syntax than a function expression and does not have its own this, arguments, super, or new.target. These function expressions are best suited for non-method functions, and they cannot be used as constructors.

Arrow Functions lexically bind their context so this actually refers to the originating context.

In ES3/4 functions declaration you can use this by storing in some other variable.

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}`;
  }
查看更多
等我变得足够好
3楼-- · 2019-08-27 11:24

You need to use bind in your class's constructor with the ES6 class method. In essence, arrow functions do this for you automatically.

constructor(props) {
  super(props);

  this.onSubmit = this.onSubmit.bind(this);
}

The more important thing to note here is that I believe the arrow function here will be created on each instance of the class, where the ES6 class method will be made part of the class's prototype and shared amongst all instances.

查看更多
Animai°情兽
4楼-- · 2019-08-27 11:27

Also, how do I do it the other way? Say, if I want to use the same onSubmit function (ES6 class method) but want to handle this when I call it (in the form element), how do I do it ?

Using this.onSubmit.bind(this) ?

Yes you must bind the method to the component in the constructor. It's because the arrow functions get automatically binded to the class therefore the scope of this is set in the method. While onSubmit is a regular function that is not yet binded therefore the this inside the method will reference the function and not the component.

查看更多
贼婆χ
5楼-- · 2019-08-27 11:33

It's important to know that this syntax:

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

is just syntactic sugar for creating an instance method in the class constructor:

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

Note: This syntax is not an official part of the JavaScript language yet (currently in stage 3) so you must use a transpiler like Babel to handle it.

The value of this within method is the class A because that is what this points to in the constructor (since arrow functions inherit the context from the scope they are defined in):

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

const instance = new A();
console.log(instance.method() === instance); // true

Defining a regular (non-arrow function) method on the class creates a method on the class prototype (not instance) but sets no rules on what this will be (since this is dynamic in JS and depends on how a function is called, not how it's defined).

class A {
  method() {}
}

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

If methods defined in either of these ways are called on the class instance (via the .), as per the rule of how this is bound when a function is called as a method of an object, this will point to the class instance in both cases:

class A {
  constructor() {
    this.methodOnInstance = () => this;
  }
  methodOnPrototype() { return this; }
}

const instance = new A();
console.log(
  instance.methodOnInstance() === instance.methodOnPrototype(), // true
  instance.methodOnPrototype() === instance // true
);

One major difference between the two method declarations above is that the instance method has this always fixed to the class instance while the class (prototype) method does not (we can change it by using Function.prototype.apply or Function.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
);

A common occurrence where the this changes is within an event handler, where the event handler calls the function passed into it and binds the context to the element on which the event happened (so overrides the value of this to be the element that was clicked or whatever the event was)

This happens in React as well for all (synthetic) DOM event handlers.

Therefore, if we want our method's context to always point to the instance of the React component, we can use the instance method.

Another way of restricting the context but not using the special instance method syntax that requires Babel is to directly create an instance method ourselves by creating a new function from the class (prototype) method with a bound context (using 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
);

This allows us get arrive to the same result as using the special instance method syntax but with the currently available tools (ES2017 and under).

If for some reason we want a method that is always bound to something that is not an instance of the class, we can do that as well:

class A {
  constructor() {
    this.method = this.method.bind(console);
  }
  method() { return this; }
}

const instance = new A();
console.log(
  instance.method() === console // true
);

查看更多
可以哭但决不认输i
6楼-- · 2019-08-27 11:35

The key difference is that in ES5 we don't have auto binding which means you have to bind your event handler function manually in order to play with state or props inside the function in react. But in ES6 it does auto binding. That's the key difference

ES5: you have to bind onSubmit preferably in constructor

//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:

The below is valid because it does auto binding.

onChange = (e) => this.setState({term: e.target.value})
查看更多
登录 后发表回答