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 andmodule.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 areFunction
's prototype methods, you can designatethis
binding explicitly by passingthis
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.
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)"