JavaScript this

this is not unique to JavaScript. In other languages however, this always point sat instance of a class. On the other hand, in JavaScript, this is determined as execution context is created(๐Ÿ”— Execution Context). Most of the execution context, with one exception of global execution context, is created on function call. The same this can refer to different things, depending on how the function is called. This makes this in JavaScript problematic.

JavaScript's this binding can be categorized into 5 patterns:

  • On global code execution, this refers to global object in browser and module.exports in node.js.
  • On method invocation, this references the object that owns the method, which comes before property accessor(dot (.) or ([])).
  • On free function call, this designates a global object.
  • When a function is invoked as a constructor, with new keyword, this refers to the object created by the function.
  • Inside a callback function, what this means is determined by the function to which the callback was passed. If not specified, this refers to global object by default.
  • On indirect invocation inside apply(), bind(), which are Function's prototype methods, you can designate this binding explicitly by passing this object as the first argument.

Global Code Execution

In browser, global object refers to global object window. Properties defined on this object is added to window as well.

In node.js, this in global execution context is bound to module.exports. module.exports is a built-in object that represents current file as a module. Whatever is assigned to module.exports object is exposed as a module.

It seems asymmetrical that node.js's global execution context does not bind this to global object global, an equivalent to browser's window object. Indeed, node.js this does point at global object, but in global execution context but in function execution context. We'll go over this soon.

Method Invocation

When a function is called as a method of an object, this inside the function refers to the object that called the method.

The same goes for both browser and node.js environment.

Free Function Call

When a function is called as a free function, namely not as a method of an object, this in the function again refers to global object.

In browser environment, it's global window object,

and global in node.js.

โš ๏ธ
It should be noted that the difference between free function call and method invocation lies in how the function is called, not where the function is.

Take the following code, for example.

Even though drive function is inside the car object, this only points to car when car calls it(car.drive()). When called as a free function(freeDrive()), without property accessor, this refers to global object. It is designed as if JavaScript regards freeDrive() is invoked as a method of global object(window.freeDrive()). To enhance understanding, ย take the following code where function declaration in global code is added to window object as its property.

Sometimes, however, that free function invocation always binds this to global object is not very instinctive. Often we expect this to point to geologically proximate, just like how 'this' does in natural language.

For example, in this code:

const car = {
  model: "Hyundai",
  speed: 4,
  drive: function (distance) {
    function getTime(distance) {
      // console.log(this.speed) // undefined
      return distance / this.speed;
    }
    console.log(`It takes ${this.model} ${getTime(distance)}h to get there`);
  }
};

car.drive(4); // โŒ It takes Hyundai NaNh to get there

getTime() fails to read this.time because getTime() is called as a free function, even though it's inside the car object.

To prevent a function from binding this to global object, one might consider using arrow function. When arrow function's ย execution skips this binding. As a result, arrow function doesn't have its "own" this. When referenced, this is borrowed from the execution context of the nearest, non-arrow function.

const car = {
  model: "Hyundai",
  speed: 4,
  drive: function (distance) {
    const getTime = (distance) => distance / this.speed;
    console.log(`It takes ${this.model} ${getTime(distance)}h to get there`);
  }
};

car.drive(4); // ๐ŸŸข It takes Hyundai 1h to get there

Now, this inside getTime() references this of outer function drive, which is car. It successfully calculates time needed to arrive at the destination.

Before arrow function was introduced in ES6, a common pattern to circumvent the same problem was to create a variable that points to this:

const car = {
  model: "Hyundai",
  speed: 4,
  drive: function (distance) {
    var speed = this.speed;
    var getTime = function (distance) {
      return distance / speed;
    };
    console.log(`It takes ${this.model} ${getTime(distance)}h to get there`);
  }
};

car.drive(4); // ๐ŸŸข It takes Hyundai 1h to get there

By convention, names such as self, _this were often used to point to this as a whole.

Constructor Invocation

A function serves as a constructor when invoked with new keyword. Inside the constructor function, this is the object the function is going to create .

In the above code, the function implicitly creates this = {}, adds properties to it( ย this.model = model), and returns it on constructor call (For further reading, see ๐Ÿ”— JavaScript Prototype - Prototype Chain),

Callback

A callback function is passed as an argument to another function. As the function given the callback decides when to call the callback, a callback passes over its control to another function. Also, what this inside the callback will be is determined by the design of the caller function.

For instance, addEventListener() lets its callback inherit its own this. Since addEventListener is attached to HTMl element, ย this inside addEventListener()'s callback is whatever comes before addEventListener's property accessor.

Some JavaScript APIs let developers designate this. They take this object as arguments, along with a callback function. Consider interfaces of some of the built-in prototype methods of Array object.

Array.prototype.forEach(callback[, thisArg])
Array.prototype.map(callback[, thisArg])
Array.prototype.filter(callback[, thisArg])
...

The above code breaks without this as second argument of .forEach(), since then this no longer points to HR object and therefore cannot find properties such as employees.

If no this is passed as arguments or a caller's API has no specific behavior regarding this, a callback's this refers to global object by default. For example, setTimeout() doesn't have fixed rule regarding this and therefore its callback's this refers to window object.

Indirect Invocation

Using call(), apply(), bind() methods of Function.prototype, you can explicitly bind this of your choosing instead of letting it be determined by the surrounding context. They all take this as their first argument.

References

์ฝ”์–ด ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ(Core JavaScript), ์žฌ๋‚จ, ์ •. ์œ„ํ‚ค๋ถ์Šค, 2019. pp. 75-93.
Modern JavaScript Tutorial - Object methods, "this"
Zerocho, "Node์—์„œ์˜ this (this in Node)"

Subscribe to go-kahlo-lee

Donโ€™t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe