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 reference to the context inside nested functions.

classService{
  constructor(){
    this.numbers = [1,2,3];
    this.token = "token";
  }
  doSomething(){
    setTimeout(functiondoAnotherThing(){
      this.numbers.forEach(functionlog(number){
      //Cannot read property 'forEach' of undefinedconsole.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(functiondoAnotherThing(){
      this.numbers.forEach(functionlog(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 specific value of this .

function doAnotherThing () {/*...//b.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(functiondoAnotherThing(){
      that.numbers.forEach(functionlog(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 name of a variable:

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:

classService{
  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);
functionrun(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 context reference when methods are used as callbacks for events.

classTodoAddFormextendsReact.Component{
  constructor(){
      super();
      this.todos = [];
  }
  componentWillMount() {
    this.setState({desc: ""});
  }
  add(){
    let todo = {desc: this.state.desc}; 
    //Cannot read property 'state' of undefinedthis.todos.push(todo);
  }
  handleChange(event) {
     //Cannot read property 'setState' of undefinedthis.setState({desc: event.target.value});
  }
  render() {
    return<form><inputonChange={this.handleChange}value={this.state.desc}type="text"/><buttononClick={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:

functionService() {  
  let numbers = [1,2,3];
  let token = "token";
  functiondoSomething(){
   setTimeout(functiondoAnotherThing(){
     numbers.forEach(functionlog(number){
        console.log(number);
        console.log(token);
      });
    }, 100);
  }
  returnObject.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 .

Also popular now: