Easy handling of "this" in JavaScript

Keywords: Javascript React Vue git

Author: Dmitri Pavlutin

Translator: Crazy Technology House

Original text: https://dmitripavlutin.com/fi...

Reproduction is strictly prohibited without permission

I like the feature in JavaScript that can change the execution context of a function (also known as this).

For example, you can use array methods on objects like arrays:

const reduce = Array.prototype.reduce;

function sumArgs() {
  return reduce.call(arguments, (sum, value) => {
    return sum += value;
  });
}

sumArgs(1, 2, 3); // => 6

But on the other hand, this keyword is difficult to grasp.

You may often check the reason why this value is incorrect. The following sections will teach you some simple ways to bind this to the required values.

Before I start, I need an auxiliary function execute(func). It is only used to perform functions as parameters:

function execute(func) {
  return func();
}

execute(function() { return 10 }); // => 10

Now let's continue to understand the nature of the error around this: method separation.

1. Method separation

The Person class contains fields firstName and lastName. In addition, it also has the getFullName() method, which returns the full name.

One possible implementation of Person is:

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;

  this.getFullName = function() {
    this === agent; // => true
    return `${this.firstName} ${this.lastName}`;
  }
}

const agent = new Person('John', 'Smith');
agent.getFullName(); // => 'John Smith'

You'll see the person function called as a constructor: new Person('John ',' Smith '). Create a new instance inside the Person function.

agent.getFullName() returns the full name of person:'John Smith'. As expected, this in the getFullName() method is equivalent to agent.

What happens if the help function executes the help.getFullName method:

execute(agent.getFullName); // => 'undefined undefined'

The execution result is incorrect:'undefined undefined'. This problem is caused by the incorrect value of this.

Now, in the method getFullName(), the value of this is the global object (window in the browser environment). Assuming this equals window, the evaluation of ${window.firstName} ${window.lastName} is undefined undefined.

This happens because the method is separated from the object when execute(agent.getFullName) is called. Basically, it just happens on regular function calls (not method calls):

execute(agent.getFullName); // => 'undefined undefined'

// is equivalent to:

const getFullNameSeparated = agent.getFullName;
execute(getFullNameSeparated); // => 'undefined undefined'

This effect is what I call the separation of objects. When a method is separated and subsequently executed, it has nothing to do with its original object.

To ensure that this in the method points to the correct object, you must:

  1. Execute this method as a property accessor: agent.getFullName()
  2. Or statically bind this to the included object (using arrow functions, the. bind() method, etc.)

In the method separation problem, the return this is incorrect, which appears in the following different forms:

When setting callback

// `this` inside `methodHandler()` is the global object
setTimeout(object.handlerMethod, 1000);

When setting up event handlers

// React: `this` inside `methodHandler()` is the global object
<button onClick={object.handlerMethod}>
  Click me
</button>

Let's continue to understand some useful ways to solve the problem of keeping the method pointing to the desired object even if it is separate from the object.

2. Close the context

The easiest way to point this to a class instance is to use the additional variable self:

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;

  const self = this;

  this.getFullName = function() {
    self === agent; // => true
    return `${self.firstName} ${self.lastName}`;
  }
}

const agent = new Person('John', 'Smith');

agent.getFullName();        // => 'John Smith'
execute(agent.getFullName); // => 'John Smith'

getFullName() closes the self variable statically, effectively binding to this manually.

Now, when you call execute(agent.getFullName), you return'John Smith', because the getFullName() method always has the correct this value, so it works.

3. Semanticize this using arrow function

Is there a way to statically bind this without any other variables? Yes, that's what the arrow function does.

To use the arrow function, let's reconstruct Person:

function Person(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;

  this.getFullName = () => `${this.firstName} ${this.lastName}`;
}

const agent = new Person('John', 'Smith');

agent.getFullName();        // => 'John Smith'
execute(agent.getFullName); // => 'John Smith'

The arrow function binds this lexically. In short, it uses this value of the external function defined therein.

I recommend using arrow functions in all cases where external function contexts are required.

4. Binding context

Let's take another step forward and use the ES2015 class to reconstruct Person.

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

const agent = new Person('John', 'Smith');

agent.getFullName();        // => 'John Smith'
execute(agent.getFullName); // => 'undefined undefined'

Unfortunately, even with the new class syntax, execute(agent.getFullName) still returns'undefined undefined'.

In the case of classes, you cannot use additional variable self or arrow functions to fix the value of this.

But there's one involved. bind() The technique of the method, which binds the context of the method to the constructor:

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;

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

  getFullName() {
    return `${this.firstName} ${this.lastName}`;
  }
}

const agent = new Person('John', 'Smith');

agent.getFullName();        // => 'John Smith'
execute(agent.getFullName); // => 'John Smith'

this.getFullName = this.getFullName.bind(this) in the constructor binds the getFullName() method to a class instance.

Excute (agent. getFullName) works properly and returns to'John Smith'.

5. Fat Arrow Method

The above method of using manual context binding requires boilerplate code. Fortunately, there is still room for improvement.

You can use JavaScript Class field recommendation To define the fat arrow method:

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  getFullName = () => {
    return `${this.firstName} ${this.lastName}`;
  }
}

const agent = new Person('John', 'Smith');

agent.getFullName();        // => 'John Smith'
execute(agent.getFullName); // => 'John Smith'

The fat arrow function getFullName =() => {...} is bound to the class instance, even if you separate the method from its object.

This is the most efficient and concise way to bind this in a class.

Six. Conclusion

There are many misunderstandings about this by separating it from objects. You should be aware of the impact.

To statically bind this, you can manually use an additional variable self to save the correct context object. But the better choice is to use the arrow function, which is naturally designed to bind this lexically.

In classes, you can use the bind() method to manually bind class methods inside the constructor.

If you want to skip writing boilerplate code, the new JavaScript suggested class fields bring fat arrow methods that automatically bind this to class instances.

Wechat Public Number: Front-end Pioneer

Welcome to scan the two-dimensional code, pay attention to the public number, and push you fresh front-end technical articles every day.

Welcome to other great articles in this column:

Posted by doug007 on Mon, 14 Oct 2019 18:04:45 -0700