Back

JavaScript's 'this' keyword explained and demystified

JavaScript's 'this' keyword explained and demystified

The this keyword is a double-edged sword—it can be a source of complicated errors and make life easier for you as a developer once you know how it 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 differs based on context.

So, in this article, the this keyword will be explained to give you a good understanding of how it works.

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.

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 references or points to depends on the context this is used. Conceptually, this is akin to a pronoun in English 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 that statement, the pronoun “she” is used to refer to the antecedent (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 the pronoun “she” used to refer to “Mary”.

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 to its malicious nature. Read this article to learn more about the Execution Context.

this in the Global Execution Context

There are differences in the Global Execution Context if we work in a browser or Node; let’s see both cases.

In a Browser

In the Global Execution Context, the this keyword references the global object, 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 of whether Javascript is in strict or non-strict mode.

In Node

As per NodeJS documentation

In browsers, the top-level scope is the global scope. That means that if you’re in the global scope var, something will define a global variable in browsers. 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.

The above statement means that the this keyword does not reference the global object in NodeJS. Instead, it points to the current module it is being 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'}

1

In the preceding code, an empty object is first 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.

Accessing 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

2

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

Open Source Session Replay

OpenReplay is an open-source, session replay suite that lets you see what users do on your web app, helping you troubleshoot issues faster. OpenReplay is self-hosted for full control over your data.

replayer.png

Start enjoying your debugging experience - start using OpenReplay for free.

Function Execution Context

In the Function Execution Context, how the this keyword is determined depends on how a function is called or 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 few 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

3

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 not a method of that object. call() and apply() also allow you to specify the arguments for the invocation. The call() method uses its 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 objects 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, and 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. 4

  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 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 the this keyword works and the different contexts it applies. You should be able to use it comfortably when required.

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 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

A TIP FROM THE EDITOR: For more on JavaScript’s internal details, don’t miss other articles by the same author: JavaScript Types and Values, explained, [JavaScript type conversions explained](https://blog.openreplay.com/javascript-type-conversions-explained, and Explaining JavaScript’s Execution Context and Stack.