JavaScript: Simplifying 'this'

The Basics

this in any scope is equivalent to the object, implicitly it's the object from it is being call called as you must have read this depends on the call site, how and where the function is being called. Explicitly you can set this too, using bind, call or apply we will cover these later.

A simple example

var obj = {
  age: 2,
  printAge: function () {
    console.log(this.age);
  },
};

obj.printAge(); // 2

Arrow functions

Arrow functions are not just syntactical sugar over the normal function apart from other differences, one major difference is value of this it follows the lexical static binding i.e. this is not dynamic anymore. this is same as this of outer lexical scope. What does outer lexical scope mean?

Scope of the the parent function!

const x = {
  y: 2,
  x1: {
    m1: () => console.log(1, this),
  },
  x2: function () {
    const y2 = {
      m1: () => console.log(2, this),
      m2: function () {
        const y3 = {
          m3: () => console.log(3, this),
        };
        return y3;
      },
    };
    return y2;
  },
};

x.x2().m1(); // 2, {y: 2, x1: Object, x2: ƒ x2()}
x.x2().m2().m3(); // 3, {m1: ƒ m1(), m2: ƒ m2()}

All function which are defined on top level are defined on window object hence this === windows for top level functions. this is undefined with strict mode enabled or with modules.

Let's take an example

var a = 1;

function incA() {
  this.a = this.a + 1;
}

incA();

console.log(a); // 2

Note: The above example holds only for "script" not "module", it will work in browser console's but not in CodeSandBox for the same reason.

Class and "this"

There are some rules, relevant one for our discussion are

  1. Arrow function can't be used as constructor.
  2. this in arrow will be equal to the instance of this class. How? Checkout: Transpiled code sample for Class

Let's take an example

class X {
  name = 2;
  method1() {
    console.log(this.name);
  }
  method2 = () => {
    console.log(this.name);
  };
}

const z = new X();
z.method1(); // 2
z.method2(); // 2

let's add two more method

class X {
  name = 2;
  method1() {
    console.log(this.name);
  }
  method2 = () => {
    console.log(this.name);
  };
  method3() {
    this.method1();
  }
  method4 = () => {
    this.method2();
  };
}

const z = new X();
z.method3(); // 2
z.method4(); // 2

Still nothing changes as method3 is being called for the the object (z) itself so it got the context and method4 has static binding.

Add the following code in the end:

const method5 = z.method3;
const method6 = z.method4;

method6(); // 2
method5(); // TypeError: Cannot read property 'method1' of undefined

As method5 has now lost the context it can't point to this, you must be wondering why it throws error instead of undefined as we discussed initially!

Module ? Nope, not this time!

It's due to implementation of class. Class are defined as function inside immediately invoked function expression.

That's why it's important to bind function in React. Not all but only those which will be passed to event handler or similar pattern as they are gonna loose the context of the component instance or use arrow functions.

Overriding "this"

There might be a case where you would like to call a method from one object with another object for example

This example will be used throughout the section

const square1 = {
  side: 5,
  getArea: function () {
    return this.side * this.side;
  },
};

const square2 = {
  side: 10,
};

console.log(square1.getArea()); // 25
console.log(square1.getArea.call(square2)); //100

// This will work as well
const getArea = square1.getArea;
console.log(getArea.call(square2)); //100

// This too
const getArea2 = square1.getArea.bind(square2);
console.log(getArea2()); //100

bind vs call

With call you can specify the object while calling a method and with each call you can pass another object.

const square3 = {
  side: 20,
};
console.log(getArea.call(square3)); //400

bind is one time contract, once a method have formed bond with an object, it can't be broken, you cannot bind that again or use call on it.

// const getArea2 = square1.getArea.bind(square2);
console.log(getArea2()); //100
console.log(getArea2.call(square3));

const getArea3 = getArea2.bind(square3); // 100
console.log(getArea3()); // 100

apply is same as call, it just expects array of parameters instead of individual parameters.

--EOF--

20