JavaScript course menu
The this Keyword in JavaScript, plus call, apply and bind
Understand this in JavaScript: how it is determined by how a function is called, methods, standalone functions in strict mode, losing this when passing methods as callbacks, arrow functions inheriting this, and fixing it with bind, call and apply.
On this page
this is one of the most confusing parts of JavaScript, because its value is not decided where a function is written, but by how the function is called. Once you know the rules, it becomes predictable.
The rules#
Methods#
const user = {
name: "Asha",
greet() {
return `Hi, I am ${this.name}`;
},
};
console.log(user.greet());
const other = { name: "Ravi", greet: user.greet };
console.log(other.greet());Hi, I am Asha
Hi, I am RaviThe same function gives different results: this is whatever object it was called on.
Losing this#
Taking a method out of its object, or passing it as a callback, loses this:
const user = {
name: "Asha",
greet() {
return `Hi, I am ${this?.name}`;
},
};
const greet = user.greet; // just the function, no object
console.log(greet());Hi, I am undefinedCalled as a plain function, this is undefined in strict mode. (The example uses this?.name to avoid an error; with this.name it would throw a TypeError.) This bites most often with callbacks:
class Timer {
seconds = 0;
tick() {
this.seconds++;
return this.seconds;
}
}
const timer = new Timer();
const runLater = (callback) => callback(); // like setTimeout or addEventListener
try {
runLater(timer.tick);
} catch (error) {
console.log(error.constructor.name, "-", error.message);
}TypeError - Cannot read properties of undefined (reading 'seconds')Fix 1: arrow functions#
Arrow functions take this from where they are defined. Wrapping the call in an arrow function keeps the method attached to its object:
class Timer {
seconds = 0;
tick() {
this.seconds++;
}
}
const timer = new Timer();
const run = (fn) => { fn(); fn(); fn(); };
run(() => timer.tick());
console.log(timer.seconds);3A class field holding an arrow function is another common pattern, especially in UI code: handleClick = () => { this.count++; };. Each instance gets its own bound function.
Fix 2: bind#
fn.bind(obj) returns a new function whose this is permanently set to obj:
const user = {
name: "Meera",
greet() {
return `Hi, I am ${this.name}`;
},
};
const boundGreet = user.greet.bind(user);
console.log(boundGreet());
console.log([1].map(boundGreet)[0]);Hi, I am Meera
Hi, I am Meeracall and apply#
call and apply call a function immediately with a chosen this. They differ only in how arguments are passed:
function introduce(greeting, punctuation) {
return `${greeting}, I am ${this.name}${punctuation}`;
}
const asha = { name: "Asha" };
console.log(introduce.call(asha, "Hello", "!"));
console.log(introduce.apply(asha, ["Namaste", "."]));Hello, I am Asha!
Namaste, I am Asha.Today, spread syntax often replaces apply (Math.max(...numbers)), and arrow functions often replace bind.
Arrow functions inside methods#
Because arrow functions inherit this, they are perfect for callbacks inside methods:
const team = {
name: "Laviyx",
members: ["Asha", "Ravi"],
list() {
return this.members.map((m) => `${m} works at ${this.name}`);
},
};
console.log(team.list());[ 'Asha works at Laviyx', 'Ravi works at Laviyx' ]A regular function (m) { ... } callback here would have its own this (undefined), and this.name would fail.
this in event handlers#
In a DOM event listener written as a regular function, this is the element; with an arrow function, it is not. Using event.currentTarget is clearer and works in both:
button.addEventListener("click", function () {
this.disabled = true; // this === button
});
button.addEventListener("click", (event) => {
event.currentTarget.disabled = true;
});Practice#
Practice exercise
This counter breaks when increment is passed to run. Fix it in two different ways: once with bind, and once with an arrow function.
const counter = {
count: 0,
increment() { this.count++; },
};
function run(fn) { fn(); fn(); }
run(counter.increment);Show solution
const counter = {
count: 0,
increment() {
this.count++;
},
};
function run(fn) {
fn();
fn();
}
run(counter.increment.bind(counter));
console.log(counter.count);
run(() => counter.increment());
console.log(counter.count);2
4Key takeaway#
Finished this lesson?