I like to change the pointer of the function execution context in JS, also known as this pointer.
For example, we can use array methods on array-like objects:
const reduce = Array.prototype.reduce; function sumArgs() { return reduce.call(arguments, (sum, value) => { return sum += value; }); } sumArgs(1, 2, 3); // => 6
On the other hand, this is difficult to grasp.
We often find that we use this to point incorrectly. The following teaches you how to simply bind this to the desired value.
[Related course recommendations: JavaScript video tutorial]
Before I start, I need a helper function execute(func) that only executes the function provided as a parameter.
function execute(func) { return func(); } execute(function() { return 10 }); // => 10
Now, moving on to understanding the nature of the error surrounding this: method separation.
1. Method separation problem
Suppose there is a class Person containing fields firstName and lastName. Additionally, it has a method getFullName() which returns the full name of the person. As shown below:
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('前端', '小智'); agent.getFullName(); // => '前端 小智'
You can see that the Person function is called as a constructor: new Person('front end', 'Xiao Zhi'). This inside the function represents the newly created instance.
getfullname() returns the person's full name: 'front-end Xiaozhi'. As expected, this inside the getFullName() method is equal to agent.
What will happen if the auxiliary function executes the agent.getFullName method:
execute(agent.getFullName); // => 'undefined undefined'
The execution result is incorrect: 'undefined undefined', which is a problem caused by incorrect this pointing.
Now in the getFullName() method, the value of this is the global object (window in the browser environment). This is equal to window, and the execution result of ${window.firstName} ${window.lastName} is 'undefined undefined'.
This happens because the method is detached from the object when execute(agent.getFullName) is called. Basically what happens is just a regular function call (not a method call):
execute(agent.getFullName); // => 'undefined undefined' // 等价于: const getFullNameSeparated = agent.getFullName; execute(getFullNameSeparated); // => 'undefined undefined'
This is what is called a method being detached from its object. When a method is detached and then executed, this has no connection to the original object.
1. In order to ensure that this inside the method points to the correct object, this must be done
2. Execute the method in the form of a property accessor: agent.getFullName() or bind this statically Determine the contained object (using arrow function, .bind() method, etc.)
Method separation problem, and the resulting incorrect this pointing, generally occur in the following situations:
Callback
// `methodHandler()`中的`this`是全局对象 setTimeout(object.handlerMethod, 1000);
When setting up the event handler
// React: `methodHandler()`中的`this`是全局对象 <button onClick={object.handlerMethod}> Click me </button>
Then we introduce some useful methods, that is, if the method is separated from the object, How to make this point to the desired object.
2. Close the context
The simplest way to keep this pointing to the class instance is to use an 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('前端', '小智'); agent.getFullName(); // => '前端 小智' execute(agent.getFullName); // => '前端 小智'
getFullName() statically closes the self variable, effectively manually binding this.
Now, when calling execute(agent.getFullName), everything works fine because this inside the getFullName() method always points to the correct value.
3. Using arrow functions
Is there a way to statically bind this without additional variables? Yes, that's exactly what arrow functions do.
Use arrow functions to reconstruct Person:
function Person(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; this.getFullName = () => `${this.firstName} ${this.lastName}`; } const agent = new Person('前端', '小智'); agent.getFullName(); // => '前端 小智' execute(agent.getFullName); // => '前端 小智'
Arrow functions bind this lexically. Simply put, it uses the value from the external function this where it is defined.
It is recommended to use arrow functions in all cases where an external function context is required.
4. Binding context
Now let’s go one step further and reconstruct Person using classes in ES6.
class Person { constructor(firstName, lastName) { this.firstName = firstName; this.lastName = lastName; } getFullName() { return `${this.firstName} ${this.lastName}`; } } const agent = new Person('前端', '小智'); agent.getFullName(); // => '前端 小智' execute(agent.getFullName); // => 'undefined undefined'
Unfortunately, even with the new class syntax, execute(agent.getFullName) still returns "undefined undefined".
In the case of classes, using an additional variable self or an arrow function to fix the pointing of this will not work.
But there is a trick involving the bind() method, which binds the context of the method into 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('前端', '小智'); agent.getFullName(); // => '前端 小智' execute(agent.getFullName); // => '前端 小智'
this.getFullName in the constructor = this.getFullName.bind( this) binds the method getFullName() to the class instance.
execute(agent.getFullName) works as expected, returning 'frontend Xiaozhi'.
5. Fat arrow method
bind method is a bit too lengthy, we can use 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('前端', '小智'); agent.getFullName(); // => '前端 小智' execute(agent.getFullName); // => '前端 小智'
Fat arrow method getFullName =() =>{…} is bound to a class instance, even if the method is detached from its object.
This method is the most effective and concise way to bind this in a class.
6. Summary
Methods separated from the object will cause the problem of incorrect this pointing. To bind this statically, you can manually use an additional variable self to hold the correct context object. However, a better alternative is to use arrow functions, which essentially serve to lexically bind this.
In a class, you can use the bind() method to manually bind the class method in the constructor. Of course, if you don't need to use the lengthy method of bind, you can also use the concise and convenient fat arrow representation method.
Original text: https://github.com/valentinogagliardi/Little-JavaScript-Book/blob/v1.0.0/manuscript/chapter5.md
This article comes from js tutorial Column, welcome to learn!
The above is the detailed content of 5 ways to correctly handle the this pointer of JS. For more information, please follow other related articles on the PHP Chinese website!