Demystifying the `this` keyword in JavaScript

Shadid Haque - Jul 7 - - Dev Community

There is a lot of confusion around the this keyword in JavaScript. The way this is described in the official MDN web docs is not very intuitive. If you feel similarly, let's learn about this keyword using some practical examples.

What is this ?

The this keyword refers to the context where a piece of code should run. If we console log the following in our browser what do we get?

console.log(this)
Enter fullscreen mode Exit fullscreen mode

The result of running the above code is the entire browser Window object. If you run it in Node instead, you get the global object. Therefore, this represents the context of where the code is defined.

Let's take a look at an example. Below we have an object called student and inside the object we will define a function called testFunction.

const student = {
  name: "Jon",
  phone: 415887231,

  testFunction: function () {
    console.log(this);
  },
};
student.testFunction();
Enter fullscreen mode Exit fullscreen mode

Output:


{
    name: "Jon"
    phone: 415887231
    testFunction: ƒ testFunction()
}
Enter fullscreen mode Exit fullscreen mode

We are console logging this from inside the testFunction. Since testFunction is defined inside the student object we get the entire student object as an output. The student object is the context where the function is defined.

this and the strict mode in Node

When you are using the strict mode in Node the behaviour of this is different. The following example demonstrates the behaviour.

'use strict';

// Top level
console.log(this);  // {}

function strictFunction() {
    console.log(this);  // undefined
}

strictFunction();
Enter fullscreen mode Exit fullscreen mode

In the top level in a module this is still the global object. However, inside a function this is now undefined.

Why would this be useful? There are many use cases of the strict mode. The following are some of them.

Avoiding accidental Globals:

'use strict';
x = 10; // ReferenceError: x is not defined
Enter fullscreen mode Exit fullscreen mode

Assigning a value to an undeclared variable creates a global variable. Strict mode prevents this.

Duplicates in object literals:

'use strict';

var obj = {
    prop: 1,
    prop: 2 // SyntaxError: Duplicate data property in object literal
};

Enter fullscreen mode Exit fullscreen mode

Strict mode throws an error for duplicate property names in object literals.

Catch silent errors:

In non-strict mode, assigning a value to a non-writable property does nothing silently. In strict mode, it throws an error.

// non strict mode
var obj = {};
Object.defineProperty(obj, 'x', { value: 42, writable: false });

obj.x = 9; // Fails silently, obj.x remains 42
console.log(obj.x); // 42
Enter fullscreen mode Exit fullscreen mode
'use strict';

var obj = {};
Object.defineProperty(obj, 'x', { value: 42, writable: false });

obj.x = 9; // TypeError: Cannot assign to read only property 'x' of object '#<Object>'
Enter fullscreen mode Exit fullscreen mode

How does this behave inside regular functions and arrow functions?

Understanding how this behaves in different scenarios is important for writing clean and predictable JavaScript code, especially when dealing with object methods and callbacks.

The following example shows the behaviour of this in a regular callback function that is defined inside a function.

const student = {
  name: "Jon Doe",
  printAllCourses: function () {
    const subjects = ["cs-101", "cs-202", "econ-101"];
    subjects.forEach(function (sub) {
      console.log(sub);
      console.log("Student Name", this.name);
    });
  },
};

student.printAllCourses();

// outputs

/**
cs-101
Student Name undefined
cs-202
Student Name undefined
econ-101
Student Name undefined
**/
Enter fullscreen mode Exit fullscreen mode

Notice the callback function for forEach is trying to access the name property but is returning undefined.

Now turn the callback function into an arrow function and run the code again.

const student = {
  name: "Jon Doe",
  printAllCourses: function () {
    const subjects = ["cs-101", "cs-202", "econ-101"];
    subjects.forEach((sub) => {
      console.log(sub);
      console.log("Student Name", this.name);
    });
  },
};

student.printAllCourses();

// outputs

/**
cs-101
Student Name Jon Doe
cs-202
Student Name Jon Doe
econ-101
Student Name Jon Doe
**/
Enter fullscreen mode Exit fullscreen mode

Notice that this time it is able to access the name property as expected.

What’s actually happening here?

this inside a regular function depends on how the function is called. When a regular function is used as a callback, only then this refers to the global object (window in browsers), which is the case in the above example. Alternatively, if the function is not called as a callback function it can access the parent objects' properties using this. So, basically, a callback function is always treated as a function that is defined in the global context.

Arrow functions, on the other hand, do not have their own this context. Instead, they lexically inherit this from the surrounding code. This simply means that the arrow function has access to the properties of the object it is defined in. So this inside an arrow function is the same as this outside the arrow function.

const student = {
  name: "Jon Doe",
  printAllCourses: function () {
    const subjects = ["cs-101", "cs-202", "econ-101"];
    console.log("outside arrow func", this); // this here is the same this inside the callback arrow function below
    subjects.forEach((sub) => {
      console.log("inside arrow func", this); // same this as above
    });
  },
};
student.printAllCourses();
Enter fullscreen mode Exit fullscreen mode

Hey if you are enjoying this post and want to see more like it don't forget to give me a follow on dev.to, X or LinkedIn

How does this work in es6 class context?

In ES6 classes, this keyword is more predictable and consistent compared to traditional JavaScript functions. When using this inside methods of a class, it generally refers to the instance of the class on which the method was called.

Consider a simple ES6 class:

class Person {
  constructor(name) {
    this.name = name;
  }

  printName() {
    console.log(this.name);
  }
}

const person = new Person('Alice');
person.printName(); // Outputs: Alice

Enter fullscreen mode Exit fullscreen mode

In the example this inside the constructor method refers to the new instance of the class being created.

this inside the printName method refers to the instance of the Person class on which the printName method was called.

Now let’s take a look at an example that shows potential issues when using class methods as callbacks and how to address them:

class Person {
  constructor(name) {
    this.name = name;
  }

  printName() {
    console.log(this.name);
  }

  printNameDelayed() {
    setTimeout(function() {
      console.log(this.name);
    }, 1000);
  }

  printNameDelayedWithArrow() {
    setTimeout(() => {
      console.log(this.name);
    }, 1000);
  }

  printNameDelayedWithBind() {
    setTimeout(function() {
      console.log(this.name);
    }.bind(this), 1000);
  }
}

const person = new Person('Bob');
person.printName(); // Outputs: Bob
person.printNameDelayed(); // Outputs: undefined (or error in strict mode)
person.printNameDelayedWithArrow(); // Outputs: Bob
person.printNameDelayedWithBind(); // Outputs: Bob

Enter fullscreen mode Exit fullscreen mode

Notice, in printNameDelayed method the callback function passed to setTimeout is a regular function, so this inside it does not refer to the instance of Person. Instead, it refers to the global object (or undefined in strict mode).

Using an arrow function as the callback preserves the this context from the enclosing method, so this refers to the instance of Person. The printNameDelayedWithArrow is a possible solution to the callback problem.

Another way to solve this callback problem is through binding. In printNameDelayedWithBind we explicitly bind the callback function to this . It ensures that this inside the callback refers to the instance of Person.

Final words

I hope, this article gave you a better understanding of this.

Think of this as JavaScript's mischievous trickster—always keeping you on your toes. But now, you're in on the joke. Keep experimenting and soon this will be your trusty sidekick, not your nemesis.

So next time someone says JavaScript is confusing, just wink and say, "I know exactly who this is!" Go forth and conquer, and may the this be with you!

If you enjoyed this post give me a follow on X or LinkedIn

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player