3 tips to find the value of 'this' in javascript

laxmanvijay

laxman

Posted on November 11, 2021

3 tips to find the value of 'this' in javascript

This article is to help you deduce the value of 'this' in javascript. It is not as simple as in Java where this refers to the current object. In javascript, "this" is dynamic. To calculate the value of this, the following 3 tips are enough and it is not difficult at all. I will present the tips and give some sample snippets in the end to help you understand the idea better.

(Basic knowledge of JS is assumed)

The first tip:

Javascript is a function-scoped language.

It does not create new lexical scopes for every bracket pair. This is a common misunderstanding.

The following syntax does not create a new scope:

if (true) {
    // statements
}
Enter fullscreen mode Exit fullscreen mode

Same applies for loops, switch statements as well.

However the following statement does create a new scope:

function getName() {
    return "Jake";
}
Enter fullscreen mode Exit fullscreen mode

Note the use of function keyword here. Arrow functions do not create a new scope.

The second tip:

Starting from ES6, there are two variants for creating functions in JS:

  • Using function keyword
  • Using arrow syntax

The important difference between them is arrow function is very lightweight - it does not support prototype keyword; bind, call and apply don't work, arrow functions are not constructible and arrow functions do not create a scope.

However the most important distinction lies in how they both handle this keyword.

  • this keyword inside a normal function is bound to the object where the reference of the function is invoked.

Note: If there is no outer scope, the default scope is used, which is the global object (Window in case of browser and global in case of Node.js)

function getName() {
    return this.name
}

// will return Window.name because getName is called globally.
getName();
Enter fullscreen mode Exit fullscreen mode

One catch is "this" of global scope will be set to undefined in strict mode. (but not really the point of focus here)

  • this keyword inside an arrow function is bound to the object where the function is defined.

Note the difference between defined and invoked. More examples on this after the third one.

The third tip:

The function keyword is special. It sets its scope to the object even if it is defined using the object literal or function is defined using prototype property. And more special property of normal functions: nesting normal functions does not alter how this is resolved. Every nested function is simply treated as a top level function.

object literal syntax:

let obj = {
    fn: function() {
        // prints obj
        console.log(this)
    }
}
Enter fullscreen mode Exit fullscreen mode

However, the function using arrow syntax does not comply with the above rule (because remember js is function-scoped and not bracket-scoped).

let obj = {
    fn: () => {
        // prints Window (outer scope in this case)
        console.log(this)
    }
}
Enter fullscreen mode Exit fullscreen mode

functional literal syntax:

The extension to this rule is when you define object using the function literal.

Consider a car class

function Car() {
    this.name = "BMW";
}

Car.prototype.getName = () => this.name;

const c = new Car();

// Will return Window.name
c.getName();
Enter fullscreen mode Exit fullscreen mode

the getName is defined using arrow syntax and thus does not obey the prototype declaration and prints out Window.name

However,

Car.prototype.getName = function () {
  return this.name;
}
Enter fullscreen mode Exit fullscreen mode

will return "BMW". This is because of the nature of function keyword is to obey prototype declaration or object literal.

What happens in ES6 class syntax?

You might know this already, ES6 classes are just a sugarcoat over the function literal for defining objects.

class Car {
    name = "BMW";

    getName() {
        return this.name;
    }

}
Enter fullscreen mode Exit fullscreen mode

The above getName will return BMW because function keyword obeys function literal objects.

Arrow syntax:

class Car {
    name = "BMW";

    getName = () => {
        return this.name;
    }
}
Enter fullscreen mode Exit fullscreen mode

The arrow syntax also prints BMW because of a different and an interesting reason - since class keyword just abstracts function literal and function literal creates a scope, the getName arrow function is always bound to Car object. This is different to object literal arrow syntax case - where it was bound to the outer scope and not the object itself.

And that's it!

These are the three tips which you can follow to always deduce the exact value of this keyword.

Building up on the above idea, let's consider the below examples:

Example 1: Indirect invocation

class Car {
    name = "BMW";

    getNameFn() {
       return this;
    }

    getNameArrow = () => {
       return this;
    }
}

function printUtil(obj) {
    const util = (fn) => {
      console.log(fn());
    }

    util(obj);
}


let c = new Car();
printUtil(c.getNameFn); // prints undefined
printUtil(c.getNameArrow); // prints BMW
Enter fullscreen mode Exit fullscreen mode

If we pass a function defined inside a class to another function, the scope will change as in the example above, the first print statement produces undefined.

However, if we define an arrow function, it is always bound to where it is defined (respecting the function-scope) and thus it prints BMW.

In order to overcome this situation, js has methods like bind, apply and call which indirectly invoke the function.

printUtil(c.getNameFn.bind(c)); // prints BMW
Enter fullscreen mode Exit fullscreen mode

bind, call and apply are simple. They just call the function with the scope of any given object (This prevents function from having dynamic "this" value). Here, c.getNameFn is passed to printUtil and is bound to object "c" (It can be bound to any object for that matter). Therefore it prints BMW.

Example 2: Function Invocation

function print() {
    console.log(this)
}

print()
Enter fullscreen mode Exit fullscreen mode

Since print function is invoked directly, it will print its outer scope object which is Window object.

Example 3: IIFE

(function () {
    console.log(this)
})()
Enter fullscreen mode Exit fullscreen mode

This syntax is called Immediately Invoked function expressions (IIFE). Quite a mouthful but nothing special about them. Consider them as normal functions that get invoked.

Thus the value of this will be its outer scope object. Same as above.

Example 4: Nested Function

let obj = {
    name = "car";

    function print() {

        function util() {
            console.log(this); // prints Window
        }

        util();
    }
}

obj.print()
Enter fullscreen mode Exit fullscreen mode

The one caveat here is nesting normal functions does not modify how this resolves. (In other words, closure scope does modify this). Every nested function is simply treated as a top level function. Therefore util is still treated as a separate function and will print Window.

But as you may have guessed, this is still dynamic. You can bind it to print function using the two methods as discussed earlier:

  • use bind/call/apply keyword:
let obj = {
    name = "car";

    function print() {

        function util() {
            console.log(this); // prints obj
        }

        util.call(this);
    }
}

obj.print()

Enter fullscreen mode Exit fullscreen mode
  • Or use the arrow syntax:
let obj = {
    name = "car";

    function print() {

        const util = () => {
            console.log(this); // prints obj
        }

        util.call(this);
    }
}

obj.print()
Enter fullscreen mode Exit fullscreen mode

The reason: You guessed it! JS is function-scoped and print function created a scope and since the scope of util arrow function is based on where it is defined (respecting function-scope).

Conclusion:

  • Always remember JS is function-scoped. It will solve a lot of this keyword confusions.
  • The function keyword obeys function literal and object literal.
  • The arrow syntax obeys only function-scope nature of js.
  • Nesting normal functions do not modify how this is resolved.
  • If the value of this inside a normal function has to be bound to a specific object consider bind/call/apply.
  • Always prefer ES6 classes and Arrow syntax. Go for normal functions only when you have to extend a prototype.

More resources:

Feedbacks, questions and constructive criticisms are always welcome!

πŸ’– πŸ’ͺ πŸ™… 🚩
laxmanvijay
laxman

Posted on November 11, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related