THE JAVASCRIPT `this` KEYWORD EXPLAINED AND DEMYSTIFIED xxxxxx

THE JAVASCRIPT `this` KEYWORD EXPLAINED AND DEMYSTIFIED xxxxxx

This article was first published on the open replay blog

The this keyword is a double-edged sword—it can be a source of complicated errors—and it can also make life easier for you as a developer once you know how it really works. These days, I feel that community pushes the language to more functional paradigms. We are not using the this keyword so much. And I bet it still confuses people because its meaning is different based on context. So, in this article, the this keyword will be explained to give you a good understanding of how it really works.

INTRODUCTION

  This article is for every Javascript developer. You will learn the following:

  • What the this keyword is in Javascript?
  • What the this keyword represents in Node.
  • How the this keyword is determined in the Global and Function Execution Context.
  • Different ways a function is invoked and how it relates to this
  • How to control the value of this using call() and apply() methods.
  • How to use the bind() method.
  • How this behaves in an arrow function

TABLE OF CONTENTS

  • WHAT IS THE this KEYWORD?
  • HOW IS THE VALUE OF this DETERMINED
    • Global Execution Context
    • this keyword in Node
    • Function Execution Context
  • CONCLUSION
    • Summary of the article
  • GLOSSARY

WHAT IS THE this KEYWORD

The this keyword is a variable used to store an object reference when a function is invoked in Javascript. The object the this keyword reference or points to depends on the context this is used in. Conceptually, this is akin to a pronoun in the English language, because it is used to refer to an object—just as a pronoun refers to a noun.

For example: “Mary is running fast because she is trying to catch the bus.”

In the above statement the pronoun "she" is used to refer to the antecedent(antecedent is the noun that a pronoun refers to) "Mary". Let's relate this concept to the this keyword.

 const person = {
    name: "Mary",
    pronoun: "she",    
    Activity: function () {
        // this = person
        console.log(`${person.name} is running fast because ${this.pronoun} is trying to catch the  bus`)
    }
}

 person.Activity() //  Mary is running fast because she is trying to catch the bus

In the preceding code this is used as a reference value of the person object, just like how the pronoun "she" is used to refer to "Mary".

   

HOW IS THE VALUE OF this DETERMINED

The this keyword is determined by the Execution context it is used in. In a nutshell, the Execution context is an abstract environment used by Javascript for code execution, it is of three variants:

  1. Global Execution Context
  2. Function Execution Context

There is also the eval() Execution Context but it is rarely used due its malicious nature. Read this article to learn more about the Execution Context.

Global Execution Context

In the Global Execution Context, the this keyword references the global object, which is the window object on the web browser.

console.log(window === this ) // true

this.color = 'Green'

console.log(window.color) // Green

In the preceding code, a property is added to the global window object through the this keyword.

N/B: In the Global Execution Context the this keyword will always reference the global object regardless if Javascript is in strict mode or non-strict mode.

this keyword in Node

As per NodeJS documentation

In browsers, the top-level scope is the global scope. That means that in browsers if you're in the global scope var something will define a global variable. In Node.js this is different. The top-level scope is not the global scope; var something inside a Node.js module will be local to that module.

What the above statement means is that the this keyword does not reference the global object in NodeJS. Instead, it points to the current module it is been used in—i.e the object exported via module.exports.

For example, let's look at a hypothetical module called app.js

┣ 📄 app.js

console.log(this);

module.exports.color = 'Green';

console.log(this);

output:

┣ $ node app.js

{}

{color: 'Green'}

In the preceding code, first, an empty object is logged because there are no values in module.exports in the app.js module. Then the color property is added to the module.exports object, when this is logged again an updated module.exports object is returned with a color property.

How to access the global object in Node

We now know that the this keyword doesn't reference the global object in Node as it does in the browser. In Node the global object is accessed using the global keyword, irrespective of where the global keyword is used.

┣ 📄 app.js

console.log(global);

output:

┣ $ node app.js

// logs the node global object

The global object exposes a variety of useful properties about the Node environment.

 

Function Execution Context

In the Function Execution Context how the this keyword is determined depends on how a function is called of invoked.

A Javascript function can be invoked in four ways:

  • Invocation as a function
  • Invocation as a method
  • Invocation as a constructor
  • Invocation with the apply and call methods

Invocation as a function

When a function is invoked as function(i.e when a function is called using the () operator), this references the global window object in non-strict mode and is set to undefined in strict mode.

Example

function A() {
    console.log(this === window) // true
}

function B() {
    "use strict"
    console.log(this === window) // false
}

function C() {
    "use strict"
    console.log(this === undefined) // true
}

A(); // true
B(); // false
C(); // true

Invocation as a method

When a function is invoked as a method(i.e via an object property), this references the method's "owning" object.

Example

let Nigeria = {
    continent: 'Africa',
    getContinent: function () {
        return this.continent;
    }
}

console.log(Nigeria.getContinent()); // Africa

Invocation as a constructor

To invoke a function as a constructor, we precede the function invocation with the new operator.

Example

function Context() {return this; }

new Context();

When a function is invoked as a constructor(via the new operator) a couple of special actions take place:

  1. A new empty object is created
  2. This object is passed to the constructor as the this reference object—i.e the object this will point to when the function is invoked.
  3. The newly constructed object is returned as the new operator's value.

Example

function Person() {
    this.name = 'Mary',
    this.age = 20    
}

const person1 = new Person();
console.log(person1.age) // 20

The preceding code and diagram show and explains how this will reference an object when a function is invoked as a constructor.

If you try to invoke a constructor function without the new operator, this will point to undefined, not an object.

Example

function Person() {
    this.name = 'Mary',
    this.age = 20    
}

const person2 = Person();
console.log(person2.age) 
// // => TypeError: Cannot read property 'age' of undefined

To make sure that the Person() function is always invoked using constructor invocation, you add a check at the beginning of the Person() function:

function Person() {
    if (!(this instanceof Person)) {
        throw Error('Must use the new operator to call the function');
    }

    this.name = 'Mary',
    this.age = 20    
}

const person2 = Person();
console.log(person2.age) 
// // => Must use the new operator to call the function

ES6 introduced a meta-property named new.target that allows you to detect whether a function is invoked as a simple invocation or as a constructor.

You can modify the Person() function to use the new.target metaproperty:

function Person() {
    if (!new.target) {
        throw Error('Must use the new operator to call the function');
    }

    this.name = 'Mary',
    this.age = 20    
}

const person2 = Person();
console.log(person2.age) 
// // => Must use the new operator to call the function

Invocation with call and apply methods (Indirect Invocation)

Functions are objects, and like all Javascript objects, they have methods. Two of these methods, call() and apply(), invoke the function indirectly. Both methods allow you to specify explicitly the this value(object reference) for the invocation, which means you can invoke any function as a method of any object, even if it is literally not a method of that object. call() and apply() also allow you to specify the arguments for the invocation. The call() method uses its own argument list as arguments to the function, and the apply() method expects an array of values to be used as arguments. In both call() and apply() the first argument is the this keyword—which represents the object on which the function is to be invoked.

Example

function getContinent(prefix) {
    console.log(`${prefix}  ${this.continent}`);
}

let nigeria = {
    continent: 'Africa'
};
let china = {
    continent: 'Asia'
};

getContinent.call(nigeria, "Nigeria is in");
getContinent.call(china, "China is in");

Output:

Nigeria is in Africa
China is in Asia

In this example, we called the getContinent() function indirectly using the call() method of the getContinent() function. We passed nigeria and china object as the first argument of the call() method, therefore, we got the corresponding country's continent in each call.

The apply() method is similar to the call() method, but as you already know—its second argument is an array of arguments.

getContinent.apply(nigeria, ["Nigeria is in"]); 
getContinent.apply(china, ["China is in"]);

Output:

Nigeria is in Africa
China is in Asia

Arrow functions

In arrow functions, JavaScript sets the this lexically. This means the value of this inside an arrow function is defined by the closest containing "non-arrow" function. In addition, the value of this inside an arrow function can't be changed. It stays the same throughout the entire life cycle of the function.

Let's look at some examples:

let getThis = () => this;
console.log(getThis() === window); // true

In this example, the this value is set to the global object i.e., the window object in the web browser. Let's understand the preceding code more with the help of the stack and heap.

  1. The getThis arrow function is lexically scoped to the Global() "non-arrow" function that returns global window object.
  2. The this value in the getThis arrow function is the global window object since the this value of the function it is lexically scoped to point to this object.

Let's look at another example:

function confirmThis () {
    let getThis = () => this;
    console.log(getThis() === window); // true
}

confirmThis();

Output:

true

Since the this value of a "normal" function points to the global window object in "non-strict mode". The this value will also point to the window object since it is lexically scoped to the confirmThis() function. However, in "strict" mode the case is different.

function confirmThis () {
    "use strict"

    let getThis = () => this;
    console.log(getThis() === window); // true
}

confirmThis();

Output:

false

The this value of the confirmThis() function will be undefined in strict mode, same applies to the getThis arrow function.

Using the bind method

As per MDN

The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.

Example

const module = {
  x: 42,
  getX: function() {
    return this.x;
  }
};

const unboundGetX = module.getX;
console.log(unboundGetX()); // The function gets invoked at the global scope
// expected output: undefined

const boundGetX = unboundGetX.bind(module);
console.log(boundGetX());
// expected output: 42

In the preceding code the module object method getX() is invoked as a "function"(instead of a method of module) in the global scope. This causes it to lose its this reference to the module object. For this to still point the module object when the getX method is invoked as a "function" instead of a "method", it needs to be "bound" to the module object via the bind() method—const boundGetX = unboundGetX.bind(module);.

   

CONCLUSION

Now you know how this keyword works and the different contexts it applies. You should be able to use it comfortably when required.

Summary

In this article you learned the following:

  • What the this keyword is in Javascript?
  • What the this keyword represents in Node.
  • How the this keyword is determined in the Global and Function Execution Context.
  • Different ways a function is invoked and how it relates to this
  • How to control the value of this using call() and apply() methods.
  • How to use the bind() method.
  • How this behaves in an arrow function

   

GLOSSARY

Stack or CallStack: A stack is a data structure that follows the Last in First Out(LIFO) principle. However, the execution stack is a stack that keeps track of all the execution context created during code execution. The stack also stores static data in Javascript(variables and reference values). Learn more here

Heap: The heap is a data structure used for storing dynamic data in Javascript. This is where all Javascript objects are stored. Learn more here.

Lexical scope: Read this stack overflow answer for better understanding.

Javascript strict mode: MDN