The this keyword
this is a special word available inside functions. Most of the time it means "the object the function was called on", which lets one method work for whichever object it belongs to. It is also one of the most confusing parts of JavaScript, because its value is decided when the function is called, not where the function is written. This lesson goes through every case.
this in a method
When you call a function as a method, with object.method(), this inside it is the object before the dot:
const asha = {
name: "Asha",
greet() {
return `Hi, I'm ${this.name}`;
},
};
const leo = { name: "Leo", greet: asha.greet }; // the SAME function
console.log(asha.greet()); // this is asha
console.log(leo.greet()); // this is leoOne function, two answers: this follows the object in front of the dot at the moment of the call. That's what makes methods reusable.
The rule: how it's called, not where it's written
| How the function is called | this is |
|---|---|
obj.method() | obj |
method(), a plain call | undefined in strict mode and modules (the global object in old sloppy scripts) |
new Thing() | the new object being built |
fn.call(obj), fn.apply(obj), fn.bind(obj)() | obj, chosen by you |
| an arrow function | the this of the code around it (arrows have none of their own) |
| an event listener (a regular function) | the element the listener is on |
Losing this
The most common this bug: taking a method out of its object and calling it on its own. The dot is gone, so this is no longer the object:
"use strict";
const counter = {
count: 0,
increment() {
this.count++;
return this.count;
},
};
console.log(counter.increment()); // called with a dot: works
const inc = counter.increment; // the function, without its object
try {
inc(); // a plain call: this is undefined
} catch (err) {
console.log(err.name + ":", err.message);
}"use strict"; as the first line switches on strict mode, which makes a plain call's this undefined (classes and modules are always strict). Without it, old-style "sloppy" code gets the global object instead, which hides the mistake. Either way, the method has lost its object.
This happens whenever a method is passed as a callback: setTimeout(counter.increment, 1000) or button.addEventListener("click", counter.increment) both call the function later without the dot.
Fixing it: bind, arrow functions
bind makes a copy of the function with this fixed to the object you choose, however it's called later:
const counter = {
count: 0,
increment() {
this.count++;
return this.count;
},
};
const inc = counter.increment.bind(counter);
inc();
inc();
console.log(inc(), counter.count);
// a callback that keeps its this: wrap the call in an arrow function
const later = () => counter.increment();
console.log(later());Wrapping the call in an arrow, () => counter.increment(), works too, because the call inside still has its dot.
Arrow functions take this from outside
Arrow functions have no this of their own: inside one, this is whatever it was in the surrounding code. That is exactly what you want for a callback inside a method:
"use strict";
const team = {
name: "Rockets",
players: ["Asha", "Leo"],
roster() {
// the arrow uses roster's this, the team object
return this.players.map((p) => `${p} plays for ${this.name}`);
},
rosterBroken() {
// a regular function gets its own this (undefined here)
return this.players.map(function (p) {
return `${p} plays for ${this?.name}`;
});
},
};
console.log(team.roster());
console.log(team.rosterBroken());And it's exactly what you don't want for the method itself: greet: () => this.name would not see the object. Rule of thumb: regular method syntax for methods, arrows for callbacks inside them.
call and apply
call and apply call a function once, with this set to the first argument. They differ only in how the other arguments are passed:
function introduce(greeting, punctuation) {
return `${greeting}, I'm ${this.name}${punctuation}`;
}
const mei = { name: "Mei" };
console.log(introduce.call(mei, "Hello", "!")); // arguments one by one
console.log(introduce.apply(mei, ["Hey", "."])); // arguments as an array
const meiIntro = introduce.bind(mei, "Hi"); // bind can fix arguments too
console.log(meiIntro("?"));| Method | Calls the function | Arguments | Returns |
|---|---|---|---|
fn.call(obj, a, b) | now | one by one | the function's result |
fn.apply(obj, [a, b]) | now | as an array | the function's result |
fn.bind(obj, a) | later, when you call the result | fixed now, more later | a new function |
this in classes
In a class, this inside a method is the instance (the object made with new). Class bodies run in strict mode, so a method that loses its object gets undefined, and the same fixes apply:
class Timer {
constructor(label) {
this.label = label;
this.ticks = 0;
}
tick() {
this.ticks++;
return `${this.label}: ${this.ticks}`;
}
}
const t = new Timer("egg");
console.log(t.tick(), t.tick());
const loose = t.tick;
try {
loose();
} catch (err) {
console.log("lost this:", err.message);
}
const bound = t.tick.bind(t);
console.log(bound());The classes lesson covers classes in full.
this in event listeners
In the browser, a regular function used as an event listener gets the element as this. An arrow function doesn't, so use event.currentTarget instead, which works with both:
<button id="b1">Regular function</button>
<button id="b2">Arrow function</button>
<p id="out"></p>
<script>
const out = document.getElementById("out");
document.getElementById("b1").addEventListener("click", function (event) {
out.textContent = "this is the <" + this.tagName.toLowerCase() + "> with id " + this.id;
});
document.getElementById("b2").addEventListener("click", (event) => {
out.textContent = "arrow: use event.currentTarget, id " + event.currentTarget.id;
});
</script>Key points
thisis decided by how a function is called:obj.method()makesthisthe object.- Taking a method out of its object (as a callback) loses
this. bindfixesthispermanently;callandapplyset it for one call.- Arrow functions have no own
this: they use the surrounding one, which suits callbacks inside methods. - Use regular method syntax for methods; use
event.currentTargetin listeners.
Practice
- Make an object
carwithbrandand a methoddescribe()that returns a sentence usingthis.brand. Copy the method onto another object and call it there. - Show the "lost
this" bug withconst d = car.describe; d();, then fix it withbind. - In an object with an array
itemsand aname, write a method that usesmapand an arrow to return"<item> from <name>"for each item. - Use
callto run a functionfunction total(tax) { return this.price * (1 + tax); }on{ price: 100 }with a tax of 0.18.