javascript
Understanding "this" in JavaScript: An In-Depth Guide
author
Luis Paredes
published
May 23, 2023
When it comes to using JavaScript, one of the concepts that causes a lot of confusion is the keyword this
and what it represents depending on the context where it is used.
In general, this
is meant to be used a handy reference to be able to point to the current "object" when working in an object-oriented style.
As in JavaScript many values or entities can be treated as objects (and hence the phrase "everything is a object in JavaScript"), the variety of values this
can point to seems to be endless. However, if you fully grasp the essentials of the Object and Function types as well as the essentials of OOP in JavaScript and the concept of the global scope, you'll be able to intuitively understand the value of this
in any context.
In this article, we'll examine all these essentials so that by the end of it you can also claim that this
doesn't confuse you anymore.
Let's dive in!
The Global scope
In JavaScript, the global execution context is known as global scope and this scope has an object containing members that make up the stardard library we have at hand without declaring or importing any other functionality.
Hence, standard JavaScript methods such as setTimeout
and setInterval
as well as globally available objects like Math
or Object
are actually just members of this global object.
As we know from the introduction, the keyword this
points to the object of the context, therefore if we run the following code in the global scope of whatever execution context we're using, we're going to get the global object as the value of this
:
console.log(this);
Global scope in the browser
Notice that in a browser environment of execution, the top-level scope (the scope of anything that is not inside an brackets or any JS construct) corresponds to the global scope, aand as a result if you run console.log(this)
at the top-level, you'll get the global object of the environment.
The browser's global object is also known as the window object and it also contains DOM methods that available for the Window
data type and can also be accessed using the keyword window
:
console.log(window);
Global scope in Node
On the other hand, if you are working in a Node JavaScript environment, the top-level does NOT always represent the global scope.
When you write code at the top-level of a JavaScript file that will be executed by Node as a module you're essentially pointing to that module scope with this
at the top-level, therefore, if you have an index.js
containing only:
console.log(this);
and you run it like this:
node index.js
the logged value will be an empty object (because the module has no members) instead of the global object.
On the other hand, if you were to run the code like this:
node -e 'console.log(this)'
you'd get Node's global object logged to the terminal since the scope of execution is the global scope instead of the module scope.
The global object in Node is simply known by its generic name global
, which is also the keyword that can be used in any scope to set and get its members:
console.log(global);
Objects
When using this
as a value for a key in an object, it will always point to its enclosing scope, regardless of whether the object is nested.
Let's see it using the following example:
const obj = {
a: this,
b: this.a,
c: {
d: "d",
e: this,
},
};
console.log(obj.a);
console.log(obj.b);
console.log(obj.c.e);
If we run this in a Node environment as a module, the top-level scope will point to the module scope, and as a result we'll get this printed to the console
{} // the module scope object
undefined // {}.a
{} // the module scope object
On the other hand, if we were to run this code in the browser, the output would be:
window
undefined // there's no window.a
window
Functions
As functions define a scope, the behavior of the this
value may vary depending on the context:
-
When declared at the top-level scope, the value is always the global object (even in a Node module)
function bar() { return this; } console.log(bar()); // `global` in Node or `window` in browsers
-
When declared inside an object, the value will point to the object
const obj = { name: this, foo: function () { return this; }, }; console.log(obj.obj()); // obj in both browsers and Node
-
When declared inside another function, the
this
in the inner function will point to the global object and thethis
in the outer function will point to whatever scope encloses it according to the three cases mentioned hereconst obj = { fn: function outerFunction() { console.log("outerFunction:", this); // outerFunction: obj const innerFunction = () => { console.log("innerFunction", this); // innerFunction: obj }; innerFunction(); }, }; obj.fn();
Arrow functions
An important caveat of arrow functions is that they don't have their own bindings for this
, instead, they take their this
value from their immediately enclosing scope.
If we rewrite the innerFunction
in the example of the previous section, we'll notice how the value of its this
is now obj
, the reason being that obj
is the this
value of the enclosing scope (outerFunction
):
Setting the this
value with methods
The Function
data type provides three methods that allow you to modify the value of this
in the function in an imperative way: bind()
, call()
and apply()
.
bind()
allows us to set (or bind) a specific this
value for a function, providing a new function instance and without modifying the original function:
const obj1 = {
name: "John",
greet: function () {
console.log(`Hello, ${this.name}!`);
},
};
const obj2 = {
name: "Anne",
};
const boundGreet = obj1.greet.bind(obj2);
obj1.greet(); // Hello, John!
boundGreet(); // Hello, Anne!
call()
and apply()
also allow us to set a specific this
value, but calling the function instead of returning a new function to be called later. As these methods involve invoking the function, they also allow passing arguments using the following signature:
<function>.call(<thisArg>, <optionalArrayWithArguments>)
<function>.apply(<thisArg>, <optionalArg1>, ..., <optionalArgN>)
In this example you can see how to accomplish the same result using call()
and apply()
:
function greet(message, emoji) {
console.log(`${message}, ${this.name}! ${emoji}`);
}
const person = {
name: "Anne",
};
greet("Hi", "😊"); // Hi, undefined! 😊
greet.call(person, "Hi", "😊"); // Hi, Anne! 😊
greet.apply(person, ["Hi", "😊"]); // Hi, Anne! 😊
Notice that these methods only make sense to be used with regular (non arrow) functions because arrow functions don't have a this
binding of their own:
const greet = (message, emoji) => {
console.log(`${message}, ${this.name}! ${emoji}`);
};
const person = {
name: "Anne",
};
const boundGreet = greet.bind(person);
greet("Hi", "😊"); // Hi, undefined! 😊
boundGreet("Hi", "😊"); // Hi, undefined! 😊
greet.call(person, "Hi", "😊"); // Hi, undefined! 😊
greet.apply(person, ["Hi", "😊"]); // Hi, undefined! 😊
Classes and prototypes
JavaScript supports object-oriented programming through the use of prototypes and classes (which act as syntax sugar for the prototype mechanism).
The patterns associated with each style of OOP JavaScript are beyond the scope of this article, so we'll focus only on how this
behaves when dealing with classes and prototypes.
Regardless of the syntax style you choose, whenever you create an instance of a class (an instance that uses the same prototype of the class), this
always refers to the instance.
Here's an example to illustrate this using the class syntax:
class Person {
constructor(name) {
this.name = name;
}
greetAndSelfIntroduce() {
console.log(`Hi! I'm ${this.name}`);
}
}
const person1 = new Person("Louis");
const person2 = new Person("Susan");
person1.greetAndSelfIntroduce(); // Hi! I'm Louis
person2.greetAndSelfIntroduce(); // Hi! I'm Susan
and here's is the same example using one of the possible raw prototypal approaches:
function Person(name) {
this.name = name;
}
Person.prototype.greetAndSelfIntroduce = function () {
console.log(`Hi! I'm ${this.name}`);
};
const person1 = new Person("Louis");
const person2 = new Person("Susan");
person1.greetAndSelfIntroduce(); // Hi! I'm Louis
person2.greetAndSelfIntroduce(); // Hi! I'm Susan
Conclusion
In this
comprehensive guide, we have explored the concept of this
in JavaScript and its behavior in different contexts.
By understanding the essentials of this
and the object-oriented nature of JavaScript, you are now equipped to confidently work with this
in any context and harness its power in your JavaScript code!