When working with JavaScript, we might encounter the use of this
, which can sometimes be confusing. In this article, we will explore this
keyword and understand its importance when developing JavaScript applications.
1. Keyword this
The this
keyword refers to the context of a piece of code is executed. Its value depends on the context in which it appears. In the following sections, we will explore various contexts and how they affect the value of this
.
2. This in global context
In global scope, this
refers to the global object:
In browser environment,
this
refers to thewindow
object. For example:console.log(this) // Window object
In server environment (Node.js),
this
refers to theglobal
object.
3. This in function context
For regular functions, the value of this
depends on how the function is invoked. The context of a function is determined by the object that the function is accessed on.
In global scope, if a function is called directly,
this
refers to the global object. For example:function logThis() { console.log(this) } logThis(); // Window object (in browser)
This is equivalent to calling
window.logThis()
in browser environment.In an object method, if the function is called as a method of an object,
this
refers to the object itself. For example:function logThis() { console.log(this) } const obj = { name: 'object', logThis } obj.logThis(); // { name: 'object', logThis: f()}
For arrow functions, they handle this
value differently from regular functions. Unlike regular functions, arrow functions do not have their own this
. Instead, they inherit the this
value from the parent scope at the time they are defined. For example:
const obj = {
name: 'object',
logThis: () => { console.log(this) }
}
obj.logThis();
// Window object
Instead of logging the obj
object like in the previous example, the code logs the window
object. This happens because this
inside the logThis
arrow function is inherited from the scope where the obj
object is defined, which is the global scope.
Arrow functions create a closure over the this
value of their surrounding scope. This behavior makes them particularly useful for callbacks and for preserving the context of this
. Let’s examine the following example:
const objA = {
name: 'object A',
logThis: function() {
const thisValue = this;
console.log('this: ',thisValue);
setTimeout(() => {
const timeoutThis = this;
console.log('timeoutThis: ', timeoutThis)
},1000);
}
}
objA.logThis();
// this: { name: 'object A', logThis: f() }
// timeoutThis: { name: 'object A', logThis: f() }
In the code above:
When the
objA.logThis
is called, the value ofthisValue
isobjA
, as expected.In the
setTimeout
, the callback is an arrow function, so thetimeoutThis
value retains thethis
value of its parent scope, which is thelogThis
function.
Now, let’s consider another example with a regular function:
const objB = {
name: 'object B',
logThis: function() {
const thisValue = this;
console.log('this: ',thisValue);
setTimeout(function() {
const timeoutThis = this;
console.log('timeoutThis: ', timeoutThis)
},1000);
}
}
objB.logThis();
// this: { name: 'object B', logThis: f() }
// timeoutThis: Window object
In this example:
Similar to
objA
, when theobjB.logThis
method is called, thethisValue
value is correctly logged asobjB
.However, the callback inside
setTimeout
is a regular function. A regular function determines based on the object that calls them. Since thesetTimeout
is executed in global context (window.setTimeout
), thethis
value inside the callback refers to the global object (window
object), rather than the scope oflogThis
function.
4. This in class context
In a class, this
typically refers to the instance of that class. For example:
class Animal {
constructor(name) {
this.name = name;
}
getName() {
return this.name;
}
}
const duck = new Animal('Donald');
duck.getName(); // 'Donald'
const cat = new Animal('Kitty');
duck.getName(); // 'Kitty'
And this
can also refer to the class itself when working with static properties and methods. For example:
class Counter {
static count = 1;
static increase() {
this.count++;
return this.count;
}
}
console.log(Counter.increase()); // 2
console.log(Counter.increase()); // 3
5. This in DOM event handlers
When using a function as an event handler, this
inside that function will refers to the DOM element to which the event listener is attached. For example:
const button = document.querySelector("#clickMe");
function eventHandler() { console.log(this.id) };
button.addEventListener("click", eventHandler);
In the above example: when the button is clicked , this
inside the eventHandler
function refers to the button element, so the result will be:
// clickMe
However, if we use an arrow function, this
will not behave as expected. For example:
const eventHandler = () => { console.log(this.id) };
button.addEventListener("click", eventHandler);
In this case, the this
value refers to the window
object, so the result will be:
// undefined
To ensure that this
refers to the DOM elements, we should use a regular function for event handlers.
6. Changing the context of a function
JavaScript provides three methods— Function.prototype.bind
, Function.prototype.apply
and Function.prototype.call
—that enable us to to call a function with new context (this
value) or create a new function with new context based on an existing function.
Create new function with new context using
bind
method:// Syntax const newFn = fn.bind(thisArg)
Example:
// Example const objA = { name: 'Object A', getName: function() { return this.name } } objA.getName(); // Object A const objB = { name: 'Object B' } const getNameB = objA.getName.bind(objB); getNameB(); // Object B
In this example:
The
getName
method is originally bound toobjA
We use
bind
method to create a new functiongetNameB
that has its context set toobjB
. WhengetNameB
is called, it returns‘Object B‘
Call a function with new context using
apply
orcall
:// Syntax fn.apply(thisArg, [arg1, arg2, ...]) fn.call(thisArg, arg1, arg2,...)
Example:
// Example function getTotalIncome(salary, bonus) { return `${this.name}'s income: ${salary + bonus}`; } getTotalIncome() // NaN const staff = { name: 'Alice' } const salary = 1000; const bonus = 200; getTotalIncome.apply(staff, [salary, bonus]); // Alice's income: 1200 getTotalIncome.call(staff, salary, bonus); // Alice's income: 1200
In this example:
The
apply
andcall
methods are used to invoke thegetTotalIncome
function with a new context (this
set tostaff
object)The difference between
apply
andcall
is that theapply
method expects the arguments as an array, while thecall
method take arguments individually.
And it’s important to note that arrow functions do not have their own this
so we can not bind a new this
to a arrow function with these method above. For example:
const getId = () => { return this.id }
getId() // undefined
const staff = { id: 123 }
const getNewId = getId.bind(staff);
getNewId(); // undefined
getId.call(staff); // undefined
getId.apply(staff); // undefined
Alright, I believe I’ve covered the most common cases of this
keyword in JavaScript in this article. I hope this guide has been helpful and that you’re no longer confused when encountering this
in any JavaScript codebase.