Considered one of the quirkiest things in javascript is simple as hell. This article tries to elucidate this and clear out it’s elusive patches that I encountered during a recent face-off with javascript.

Prerequisites: Just shelve your understanding of classical Object Oriented this.

Concepts

Before diving in, let’s be clear on the basics. In Javascript

  • Everything is an object and functions are no exception with name, properties and code libs.
  • Each and every code runs inside contexts which are stacked up in context stack of JS Engine.
  • The global execution context(window object in the browser and global object in nodejs) resides at the bottom of the stack and stays until the code terminates.
  • Whenever a function is invoked(and not defined) a context is created and is pushed to the top of the existing stack, popping it at the end of it’s execution.
  • Every context consists of a variable environment, lexical(outer) environment and a special variable this, where this refers to the value of the object that invoked the function.
  • Reference of this inside a function is independent of it’s lexical environment i.e. the value of this inside a nested function is not determined by it’s parent’s this. It’s assigned depending on how that function is invoked.
  • Hence invoking function without a context always makes this refer to the global object by default.

Key: this is not assigned a value until an object invokes the function.

Pitfalls

Up with basics, let’s start with some cases to grab the points more clearly.

Case 1: this in the global scope.

    
    this
    this.a = 1
    a

Output

    
    Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
    1

As mentioned if no function call is made, then execution stack is just left with global context and hence this always refers to the global object.

Case 2: this inside a function, invoked without a context.

    
    function b(){
        console.log(this);
        function c(){
            console.log(this);
        }
        c();
    }
    b();
    this.b();

Output

    
    Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, }
    Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, }

Here function b(global) & function c(local to b) have been invoked without a context. When a function is called without an object, be it global or local, it’s this always refers to the global context and hence the global object of the environment.

It’s not because of c() accessing the value of b()’s this, that they have the same value, but rather because of invoking without an object.

Case 3: this inside a method of an object

    
    var d ={
        e: 1,
        f: function(){
            console.log(this.e);
        }
    }
    d.f();

Output

    1

Here function f() is called with an object d which would be passed to the newly created context and hence this here refers to the method’s object.


Above three cases might seem obvious and not much of interest, so let’s look at the tricky ones.

Case 4: this within closures. or simply put, this inside a function defined within an object’s method, invoked without a context. Stay calm and see the example :)

    
    var g ={
        h: 1,
        i: function(){
            console.log(this);
            function j(){
                console.log(this);
            }
            j();
        }
    }
    g.i();

Output

    
    {h: 1, i: ƒ}
    Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, }

Here function i() has been invoked by an object ‘g’ and hence it’s this refers to the object which invoked the function. Case 3 already confirms it’s validity. However what about function j(), which is defined and invoked inside i() without an object. Well, going by the classical understanding of languages we might assume that because j() lies in the context of i(), it would surely have access to this of i() and would have the same value in and out. But here is the catch, this is a special property and is not available as is. It’s value is made available at the time of function execution and is derived from the object that invoked the function.

At the time of execution function j() (local to i()) was invoked without specifying an object context and hence as mentioned in case 3, it’s this refers to the global object and hence the output.

Case 5: this inside a callback. i.e. callback with some object’s method

    
    var user = {
        data: [
            { name: "Ananya", age: 23 },
            { name: "Viraj", age: 27 }
        ],
        clickHandler: function (event) {
            console.log(this.data[0].name + " " + this.data[0].age);
        }
    }
    user.clickHandler(); 
    $ ("button").click (user.clickHandler); 

Output

    
    Ananya 23
    Cannot read property '0' of undefined

The button is wrapped inside a jQuery $ wrapper, so it is now a jQuery object. Here we expect to log user’s data property on click of the button. ClickHandler, being a method of user object needs to be prefixed by ‘user.’ to identify and access it. The first invocation of the method is via object user, so during it’s execution, this refers to the user object. However, the second invocation is via the button object, and so it’s reasonable to assume that the button object is passed to the execution context and hence this refers to the button object.

The clickHandler is prefixed with the user just in order to identify and get access to the function but invocation it’self is being driven by the invoking object. Now, since there is no data property on the button object, the output will be undefined.

Case 6: this within method assigned to a variable i.e. callback with some object’s method

    //global variable
    var data = [
        { name: "Sumit", age: 12 },
    ];
    var user = {
        data: [{ name: "Ananya", age: 23 },],
        showData: function (event) {
            console.log(this.data[0].name + " " + this.data[0].age);
        }
    }
    var showUserData = user.showData;    
    showUserData();

Output

    Sumit 12

The this variable is bound to another object if we assign a method that uses this to a variable. On executing the showUserData function, the values printed to the console are from the global data array, and not from the data array in the user object. This is again because the user’s showData is being assigned to the global variable showData, however invocation is being done without an object. hence the output.

Case 7: this when borrowing methods.

    
    var gameController = {
        scores: [20, 34, 55, 46, 77],
        avgScore: null,
        players: [
            { name: "Tommy", playerID: 987, age: 23 },
            { name: "Pau", playerID: 87, age: 33 }
        ]
    }
    var appController = {
        scores: [900, 845, 809, 950],
        avgScore: null,
        avg: function () {
            var sumOfScores = this.scores.reduce(function (prev, cur, index, array) {
                return prev + cur;
            });
            this.avgScore = sumOfScores / this.scores.length;
        }
    }
    gameController.avgScore = appController.avg();
    console.log(gameController.avgScore);
    console.log(appController.avgScore);

Output

    
    null
    876

Here we might expect the gameController object to use the appController avg method to compute the avgscore of the gameController object. However when executing the function, it is being invoked by the appController it’self and hence is passed to the object as this.

Conclusion

So this sums up the typical pitfalls of this. Even though the concept is plain and simple, but chances are quite likely of making such mistakes. then what’s the way out?

  • One of the most common practices among developers and well adapted within frameworks is to store a reference to this at the start of the function/method and use it whenever a reference to this is required. It simply eliminates the independent nature of this inside a function and hence can be used like regular OO languages.
    var g ={
        h: 1,
        i: function(){
            console.log(this);
            var self = this;
            function j(){
                console.log(self);
            }
            j();
        }
    }
    g.i();

Output

    1
    1
  • As of ES5, if we have strict mode enabled, JavaScript will do the right thing and instead of defaulting to the window object it will just keep this as undefined.
  • The final way of course is to use functions like call(), apply(), bind() which allow us to exclusively pass the this object as parameter which I will be discussing in my next article.