All Posts

JavaScript this keyword

📅 March 31st, 2020

⏱️ 7 min read

Updated: June 17th, 2020

JavaScript this keyword

This

A function's this keyword behaves a little differently in JavaScript compared to other languages. It also has some differences between strict mode and non-strict mode.

Before diving into the specifics of the this keyword in JavaScript, it’s important to know why the this keyword exists in the first place. The this keyword allows you to reuse functions with different contexts. Said differently, the this keyword allows you to decide which object should be focal when invoking a function or a method. Everything we talk about after this will build upon that idea. We want to be able to reuse functions or methods in different contexts or with different objects.

Global context

In the global execution context (outside of any function), this refers to the global object whether in strict mode or not.

// In web browsers, the window object is also the global object:
console.log(this === window); // true

a = 28;
console.log(window.a); // 28

this.b = "U";
console.log(window.b); // "U"
console.log(b); // "U"

Function context

In most cases the value of this differs depending on how a function is invoked (the only exception is bind and arrow functions), so we can’t know the value of this just by looking at the function definition, but we need to know the context in which the function is invoked and how it was invoked.

There are 5 rules to keep in mind. Let’s quickly review these rules.

Simple Function Call

this is the global object in non-strict mode, and undefined in strict mode.

In the case of a simple function call, in non-strict mode this will default to the global object:

function f1() {
  return this;
}

// in a browser
f1() === window; // true

// in node
f1() === global; // true

In the same scenario, but in strict mode, this will be undefined:

"use strict";
function f1() {
  return this;
}

f1() === undefined; // true

Implicit Binding

this points to the object on which the function is called (what’s to the left of the period when the function is called).

This rule will apply for the majority of cases in your day-to-day code and applies when calling a method on an object:

const cat = {
  voice: "Meow",
  speak: function() {
    console.log(this.voice);
  },
  owner: {
    name: "Jhon",
    speak: function() {
      console.log(`Hello, I'm ${this.name}`);
    }
  }
};

cat.speak(); // Meow
cat.owner.speak(); // Hello, I'm Jhon

Note also that we get the same result if our object only contains a reference to the function:

function speak() {
  console.log(this.voice);
}

const cat = {
  voice: "Meow",
  speak: speak
};

cat.speak(); // Meow

Explicit Binding

We can explicitly tell the JavaScript engine to set this to point to a certain value using call, apply or bind.

call and apply can be used to invoke a function with a specific value for this as the first argument. The difference between them is that, call passes in arguments one by one to the function, apply passes in a single array that will be spread out for you as arguments to the function:

function speak(a, b) {
  console.log(this.voice + a + b);
}

const cat = {
  voice: "Meow"
};

speak.call(cat, 1, 2); // Meow12
speak.apply(cat, [1, 2]); // Meow12

Both call and apply accomplish the same task, and the first argument to both should be what this points to. The difference is only apparent if additional arguments need to be passed to the invoked function. With call, the additional arguments are passed as a normal comma-separated list of arguments, and with apply an array of arguments can be passed-in.

bind is used to create a new function that’s permanently bound to a this value. In the following example, we create a new function that has its this permanently bound to cat and re-assign speak to that new permanently bound function:

function speak() {
  console.log(this.voice);
}

const cat = {
  voice: "Meow"
};

speak = speak.bind(cat);
speak(); // Meow

new Binding

Using the new keyword constructs a new object, and this points to it.

When a function is invoked as a constructor function using the new keyword, this points to the new object that’s created:

function Cat(name) {
  this.name = name;
}

const myCat = new Cat("Kitty");
console.log(myCat.name); // Kitty

If the constructor function has a return statement (this is rare) that returns an object, that object will be the result of the new expression. Otherwise, the result of the expression is the object currently bound to this:

function Cat(name) {
  this.name = name;

  return {
    name: "Another Kitty"
  };
}

const myCat = new Cat("Kitty");
console.log(myCat.name); // Another Kitty

Arrow Functions

With arrow functions, this keeps the same value as its lexical scope(parent scope).

The following example shows why we may need arrow functions:

const user = {
  name: "Ahmed",
  age: 28,
  languages: ["JavaScript", "TypeScript"],
  greet() {
    const hello = `Hello, my name is ${this.name} and I know`;

    const langs = this.languages.reduce(function(str, lang, i) {
      // the error happens in the next line because this.languages is undefined
      if (i === this.languages.length - 1) {
        return `${str} and ${lang}.`;
      }

      return `${str} ${lang},`;
    }, "");

    console.log(hello + langs);
  }
};

user.greet(); // Uncaught TypeError: Cannot read property 'length' of undefined

We expected this to be user object but it's not. First, we need to look at where the function is being invoked. Wait? Where is the function being invoked? The function is being passed to .reduce so we have no idea. We never actually see the invocation of our anonymous function since JavaScript does that itself in the implementation of .reduce. That’s the problem. We need to specify that we want the anonymous function we pass to .reduce to be invoked in the context of user. That way this.languages will reference user.languages.

We can fix this problem in two ways:

  • Set the value of this explicitly using bind:

    const user = {
    name: "Ahmed",
    age: 28,
    languages: ["JavaScript", "TypeScript"],
    greet() {
      const hello = `Hello, my name is ${this.name} and I know`;
    
      const langs = this.languages.reduce(
        function(str, lang, i) {
          if (i === this.languages.length - 1) {
            return `${str} and ${lang}.`;
          }
    
          return `${str} ${lang},`;
        }.bind(this),
        ""
      );
    
      console.log(hello + langs);
    }
    };
    
    user.greet(); // Hello, my name is Ahmed and I know JavaScript, and TypeScript.
  • Use an arrow function as the callback function passed to reduce which will use the value of this in its parent scope:

    const user = {
    name: "Ahmed",
    age: 28,
    languages: ["JavaScript", "TypeScript"],
    greet() {
      const hello = `Hello, my name is ${this.name} and I know`;
    
      const langs = this.languages.reduce((str, lang, i) => {
        if (i === this.languages.length - 1) {
          return `${str} and ${lang}.`;
        }
    
        return `${str} ${lang},`;
      }, "");
    
      console.log(hello + langs);
    }
    };
    
    user.greet(); // Hello, my name is Ahmed and I know JavaScript, and TypeScript.

Conclusion

So putting all of our rules into practice, whenever I see the this keyword inside of a function, these are the steps I take in order to figure out what it’s referencing.

  1. Look to where the function was invoked.
  2. Was the function invoked using the new keyword? If so, the this keyword is referencing the newly created object that was made by the JavaScript interpreter. If not, continue to #3.
  3. Was the function invoked with call, apply, or bind? If so, it’ll explicitly state what the this keyword is referencing. If not, continue to #4.
  4. Is there an object to the left of the dot? If so, that’s what the this keyword is referencing. If not, continue to #5.
  5. Is this inside of an arrow function? If so, its reference may be found lexically in the enclosing (parent) scope. If not, continue to #6.
  6. Are you in strict mode? If yes, the this keyword is undefined. If not, continue to #7.
  7. this is referencing the window object.

Resources


Be the First to Leave a Comment!

Leave a Comment 💬