Search code examples
callbackclosuresthisbindarrow-functions

javascript scope and everything about 'this'


I am trying to understand in depth how 'this' works in javascript. All I have known about this so far is,

  1. Every function has properties and whenever the function executes, it newly defines the this property.

  2. this refers to the object that a function is invoked to (including window object in browser).

  3. this refers to the scope of the object(where the object is defined) instead of referring to the object itself if you use arrow syntax when defining a function because arrow function does not newly defines its own this.

The examples below are to help understanding the behaviour of this

class Example {

  constructor() {
    this.name = 'John';
  }

  method1() { //case1 : Closure
    console.log(this.name);

    function method2() {
      console.log(this.name);
    }

    method2();
  }
}

const a = new Example()
a.method1();


function testing(callback) {
  return callback();
}

class Example2 {

  constructor() {
    this.name = 'John';
  }

  method1() { //case2: callback
    console.log(this.name);

    testing(function() {
      console.log(this.name);
    })
  }
}

const b = new Example2()
b.method1();


function testing(callback) {
  return callback();
}

class Example3 {

  constructor() {
    this.name = 'John';
  }

  method1() { //case3: arrow syntax callback
    console.log(this.name);

    testing(() => {
      console.log(this.name);
    })
  }
}

const c = new Example3()
c.method1(); // logs 'John'
// logs 'John'


function testing(callback) {
  return callback();
}

class Example4 {

  constructor() {
    this.name = 'John';
  }

  method1() { // case4: calling method as callback
    console.log(this.name);
  }

  render() {
    testing(this.method1)

  }
}

const d = new Example4()
d.render()


function testing(callback) {
  return callback();
}

class Example5 {

  constructor() {
    this.name = 'John';
    this.method1 = this.method1.bind(this);
  }

  method1() { //case5: bind method && calling method as callback 
    console.log(this.name);
  }

  render() {
    testing(this.method1)

  }
}

const d = new Example5()
d.render()

I wonder how those above cases are different and what the this refers to inside each inner function and callback. Could you please explain about it? thank you :)


Solution

  • Since the in-depth precise explanation can be pretty big and boring, here is an exceptional article by kangax that perfectly lays it out.

    And just in case, if you need a short and ultra condensed version of it here goes my short and approximate take:

    #

    When you call a function the this is determined by the specific base value which is usually pointing to whatever is on the left of the . in MemberExpression so in x.y() this === x, and in x.y.z() this === x.y.

    In case of a simple CallExpression without the ., say just x(), the base value is implicitly inferred to point to undefined, which in non-strict mode is converted to global window and in strict mode stays the same.

    This is the general mental model which should cover 99% of all the day-to-day problems with drawing the this context out correctly.

    Now on, to the actual cases:

    CASE 1:

    a.method1(); call has a base value a so the this inside of its body points to a, so no surprises here. method2 has implicit base value undefined.method2, thus you have the TypeError which explicitly states that.

    CASE 2:

    function testing(callback) {
      return callback();
    }
    

    callback() is called with implicit baseValue undefined, i.e. undefined.callback(), and since the passed function is declared within class

    testing(function() {
      console.log(this.name);
    })
    

    that triggers the strict mode of code execution, that's why undefined is not converted again to global window, thus we have the same error as before.

    CASE 3:

    Arrow function

    testing(() => {
       console.log(this.name);
    })
    

    creates a hard binding from the this in enclosing scope, basically under the hood it's the same as writing:

    var _this = this;
    testing((function() {
       console.log(_this.name);
    });
    

    That's why you get the same object resolved as this

    CASE 4:

    Alright, this one is interesting and needs more mechanics explanation.

    So when you pass this.method in:

    render() {
      testing(this.method1)
    }
    
    • what you actually pass is not the reference this.method, but the actual underlying Function Object value, to which this reference points to, so when it gets executed it has its this always pointing to undefined, here look, so it's pretty much "in stone".

    And yes of course since this.method1 is declared in strict context again, thanks to enclosing es6 class, undefined remains undefined without conversion to global window.

    CASE 5:

    Same mechanics as with arrow function. Bind creates a wrapper function, which holds the cached this value, which is not possible to override with .call and .apply, the same as in => function.

    Hope this clarifies a bit it all a bit.