📜 ⬆️ ⬇️

What to do when “this” loses contextual reference

Hi, Habr! I present to you the translation of the article “What to do when“ this ”loses context” by Cristi Salcescu .

The best way to avoid the loss of this context is to not use this . However, this is not always possible. For example, we work with someone else's code or library that uses this .

Object literal, constructor function, constructor of class objects in the prototype system. The pseudoparameter this is used in the prototyping system to give access to the properties of an object.
')
Let's look at a few cases.

Nested Functions


this loses the context reference inside nested functions.

class Service { constructor(){ this.numbers = [1,2,3]; this.token = "token"; } doSomething(){ setTimeout(function doAnotherThing(){ this.numbers.forEach(function log(number){ //Cannot read property 'forEach' of undefined console.log(number); console.log(this.token); }); }, 100); } } let service = new Service(); service.doSomething(); 

The doSomething () method has two nested functions: doAnotherthing () and log () . When calling service.doSomething () , this loses the reference to the context in the nested function.

bind ()


One way to solve a problem is with the bind () method. Take a look at the following code:

 doSomething(){ setTimeout(function doAnotherThing(){ this.numbers.forEach(function log(number){ console.log(number); console.log(this.token); }.bind(this)); }.bind(this), 100); } 

bind () creates a new version of a function that, when called, already has a certain value this .

function doAnotherThing () {/*...//.bind(this) creates a version of the doAnotherThing () function that takes the value of this from doSomething () .

that / self


Another option is to declare and use the new variable that / self , which will store the value of this from the doSomething () method.

 doSomething(){ let that = this; setTimeout(function doAnotherThing(){ that.numbers.forEach(function log(number){ console.log(number); console.log(that.token); }); }, 100); } 

We must declare that that = this in all methods that use this in nested functions.

Arrow functions (Arrow function)


The arrow function gives us another way to solve this problem.

 doSomething(){ setTimeout(() => { this.numbers.forEach(number => { console.log(number); console.log(this.token); }); }, 100); } 

Arrow functions do not create their own context for this , but use the this value of the surrounding context. In the example above, it uses the value of this parent function.

The disadvantage of this method is that we cannot specify the name of the arrow function. The name of the function plays an important role as it increases the readability of the code and describes its purpose.

Below is the same code with a function expressed through the variable name:

 doSomething(){ let log = number => { console.log(number); console.log(this.token); } let doAnotherThing = () => { this.numbers.forEach(log); } setTimeout(doAnotherThing, 100); } 

Callback Functions (Method as callback)


this loses the context reference when using the method as a callback function. Look at the following class:

 class Service { constructor(){ this.token = "token"; } doSomething(){ console.log(this.token);//undefined } } let service = new Service(); 

Let's take a look at the situations in which the service.doSomething () method is used as a callback function.

 //callback on DOM event $("#btn").click(service.doSomething); //callback for timer setTimeout(service.doSomething, 0); //callback for custom function run(service.doSomething); function run(fn){ fn(); } 

In all cases above, this loses the context reference.

bind ()


We can use bind () to solve this problem. Below is the code for this option:

 //callback on DOM event $("#btn").click(service.doSomething.bind(service)); //callback for timer setTimeout(service.doSomething.bind(service), 0); //callback for custom function run(service.doSomething.bind(service)); 

Arrow function


Another way is to create an arrow function that calls service.doSomething () .

 //callback on DOM event $("#btn").click(() => service.doSomething()); //callback for timer setTimeout(() => service.doSomething(), 0); //callback for custom function run(() => service.doSomething()); 

React Components (React Components)


In components, this loses the context reference when methods are used as callbacks for events.

 class TodoAddForm extends React.Component { constructor(){ super(); this.todos = []; } componentWillMount() { this.setState({desc: ""}); } add(){ let todo = {desc: this.state.desc}; //Cannot read property 'state' of undefined this.todos.push(todo); } handleChange(event) { //Cannot read property 'setState' of undefined this.setState({desc: event.target.value}); } render() { return <form> <input onChange={this.handleChange} value={this.state.desc} type="text"/> <button onClick={this.add} type="button">Save</button> </form>; } } ReactDOM.render( <TodoAddForm />, document.getElementById('root')); 

As a solution, we can create new functions in the constructor that will use bind (this) .

 constructor(){ super(); this.todos = []; this.handleChange = this.handleChange.bind(this); this.add = this.add.bind(this); } 

Do not use “this"


No this - no problem with loss of context. Objects can be created using factory functions . Take a look at this example:

 function Service() { let numbers = [1,2,3]; let token = "token"; function doSomething(){ setTimeout(function doAnotherThing(){ numbers.forEach(function log(number){ console.log(number); console.log(token); }); }, 100); } return Object.freeze({ doSomething }); } 

The context remains if you use the method as a callback.

 let service = Service(); service.doSomething(); //callback on DOM event $("#btn").click(service.doSomething); //callback for timer setTimeout(service.doSomething, 0); //callback for custom function run(service.doSomething); 

Conclusion


this loses context reference in various situations.
bind () , using the that / self variable and arrow functions are ways to solve context problems.

Factory functions allow you to create objects without using this .

Source: https://habr.com/ru/post/421959/


All Articles