Buildtide
Author: Hussain Mir Ali

I am interested in web and mobile technologies.

If you have any questions or feedback then message me at devtips@Buildtide.com.

Context in JavaScript

In this blog post I have discussed how context works in JavaScript. More precisely this blog post discusses how the value for 'this' keyword is determined.

Understanding context in JavaScript is not a trivial task for new developers and even experienced developers might not have a clear understanding. Analyzing different scenarios to isolate the effect on context makes it more digestible. Context is basically a reference to the owner object. The 'this' keyword inside a function is the context. Every function execution has a context associated with it. There are 5 scenarios where the context will be different due to the way a function is executed.

1. Object Literal

Whenever a function is invoked as a property of an Object it always has the 'this' keyword referring to the Object itself. An example will further clear things up:

let car = {
   make: 'Tesla',
   model: 'Model S',
   getModelName: function() {
      console.log(this);
      console.log(this.model);
  }
  };

The car variable is set to an object literal it has 'make', 'model' and 'getModelName' properties. When the function 'getModelName' is executed it outputs the value of 'this' to be the object itself and so it is able to access the model using this.model syntax. In this case context is the owner Object (Object literal).

car.getModelName();

//console.log(this); -> {make: "Tesla", model: "Model S", getModelName: ƒ}
//console.log(this.model); -> Model S

2. Global Variable Invocation

When a JavaScript function is declared as a global variable and invoked then its context will be the 'window' Object. In the previous example since the function was invoked from an Object literal it had 'this' value refer to the Object itself so with that intuition owner Object of a global variable will be the window Object. Therefore globally declared function will have 'this' as the 'window' Object. An example for better understanding:

var testFunc1 = function(){
console.log(this);
};

var testFunc2 = function(){
return function(){
    console.log(this);
};
};

var testFunc3 = function(){
return function(){
    return function(){
       console.log(this);
    };
};
};

The different function in the example have certain depth and it is evident that no matter what the depth is 'this' will always be the 'window' Object.

For testFunc1 it is visible that the value of 'this' is the 'window' Object.

testFunc1();
//Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}

The testFunc2 returns another function after first invocation which is assigned to variable 'tf2'. But after executing 'tf2' it displays the value of 'this' to still be the 'window' Object.

var tf2 = testFunc2();
tf2();
Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}

Similarly testFunc3 returns another function down to 2 more levels. But again the value of 'this' is the window Object which proves that no matter how many levels deep function calls go 'this' always refers to the 'window' Object if the parent function is declared globally and all subsequent function calls are invoked without 'new' prefix.

var tf3 = testFunc3();
var tf33 = tf3();
tf33();
//Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}

3. Constructor Invocation

When a function is invoked using the 'new' prefix 'this' is always the function itself. This is comparatively simpler concept to grasp. But it will be evident that it can get tricky. Lets continue with 'testFunc3' from previous example and modify it a bit:

var testFunc3 = function(){
    console.log('level1', this);
    return function testFunc33(){
        console.log('level2', this);
        return function testFunc333(){
             console.log('level3', this);
        };
    };
  };
  

In this example 'this' keyword is logged for every level. So lets see what each level displays.

var tf3 = new testFunc3();
//level1 testFunc3 {}

The 'level1' function invocation with 'new' prefix shows 'this' to be 'testFunc3' which is the function itself as expected. Now lets see the next level.

var tf33 = new tf3();
  //level2 testFunc33 {}
  

Even in 'levle2' function invocation with 'new' yields 'this' to be 'testFunc33' which is the function itself. Moving on to next level but invocation without 'new' prefix.

var tf333 = tf33();
//level3 Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, frames: Window, …}

It seems now the function has 'this' as 'window' Object and not the function itself. This is because if a context-less(not a property of parent object) function is invoked without 'new' prefix 'this' will be bound to the 'window' Object not matter how deeply nested the function is. If 'new' prefix is used while invoking 'tf333' then 'this' becomes function itself as expected.

var tf333 = new tf33();
//level3 testFunc333 {}

4. ES6 Arrow function

Compared to vanilla JavaScript the arrow function '()=>{}' in ES6 has lexical binding. This means 'this' will not be bound to 'window' Object if the immediate parent is not an Object like in the previous example. After modifying previous example to include arrow function it will be clear:

var testFunc3 = function(){
console.log('level1', this);
return function testFunc33(){
    console.log('level2', this);
    let testFunc333 = ()=>{
         console.log('level3', this);
    };
    return testFunc333;
};
};

Now 'testFunc333' is an arrow function. Lets see what it ouputs for this.

var tf3 = new testFunc3();
//level1 testFunc3 {}
var tf33 = new tf3();
//level2 testFunc33 {}
var tf333 = tf33();
//level3 testFunc33 {}

It is evident that arrow functions preserve 'this' from the parent function. So now 'level3' and 'level2' have same 'this'. This is way 'testFunc333' will no longer be bound to 'window' Object.

5. call, apply and bind

Using call, apply and bind the value for 'this' can be explicitly set. This is convenient when 'this' can get hard to manage in large applications.

The '.bind(this, arg1, arg2)' function when used on existing function will return a new function with 'this' changed and with arguments passed to the function.

var car = {make: 'Tesla', model: 'Model S'};

var printPrice = function(price){
    return this.make+" "+this.model+" is $ "+price;
};

printPrice(100000);//"undefined undefined is $ 100000"

//Setting this
printPrice = printPrice.bind(car, 100000);

printPrice();//Tesla Model S is $ 100000

The 'printPrice' function prints price of a car. The 'car' Object has information about a certain car. When 'printPrice' is invoked with price as 100000 the output has undefined in it. This is because 'this' is bound to 'window' Object. But 'make' and 'model' are not defined in 'window' Object this is why it returns undefined for those values. But when using 'bind' method the first argument 'car' sets the value of 'this' to be 'car' Object and the second argument passes price. This creates a new 'printPrice' function with 'this' and 'price' already in place.

The call and apply function are similar to each other as they directly invoke the function instead of return a new one. The only difference is that 'call' has comma seperated arguments after the first argument and 'apply' has an array argument for the second argument. Both can be used to explicitly set value for 'this' in a function and also provide arguments for the function.

var constants = {pi: 3.14159265359};

var scaleNumbers = function(){
     var groupNumbers = arguments;
     for(var i=0; i<groupNumbers.length; i++){
         groupNumbers[i] = groupNumbers[i]*this.pi;
      }
     console.log(groupNumbers);
}

scaleNumbers.call(constants, 5,2,34,234,5);
//Arguments(5) [15.70796326795, 6.28318530718, 106.81415022206001, 735.13268094006, 15.70796326795, callee: ƒ, Symbol(Symbol.iterator): ƒ]

scaleNumbers.apply(constants, [5,2,34,234,5]);
//Arguments(5) [15.70796326795, 6.28318530718, 106.81415022206001, 735.13268094006, 15.70796326795, callee: ƒ, Symbol(Symbol.iterator): ƒ]

The 'scaleNumbers' function will scale the any passed number by 'pi' using 'this.pi'. The 'constants' Object literal contains value of 'pi'. But the value of 'this' in 'scaleNumbers' function is 'window' Object so 'this.pi' will be undefined. To fix this 'call' and 'apply' can be used to set 'this' and invoke the function with numbers. The 'call' method takes first argument to be value of 'this' which is the 'constants' Object literal and second argument is comma separated list of numbers. The 'apply' method takes first argument to value of 'this' as well but the second argument is an array containing numbers.