Who Is “I”?
Read this sentence: “I am the class topper.” Who is “I”? You cannot tell from the sentence alone. If Meera says it, “I” means Meera. If Kabir reads the same sentence aloud, “I” means Kabir. The words stay the same; the meaning depends on who is speaking.
The JavaScript keyword this works exactly the same way. A function that uses this is like a sentence that uses “I”. What this points to is not decided when you write the function. It is decided each time the function is called, by how it is called.
That one rule explains almost every this puzzle. In this lesson you will see this in object methods, plain functions, callbacks, arrow functions, event handlers and classes, and learn how to control it with bind, call and apply. You should already know objects and the class syntax from the JavaScript Classes and Objects lesson.
this in Object Methods
When you call a function as a method, with an object before the dot, this is that object. The object before the dot is the speaker.
const aarav = {
name: "Aarav",
city: "Pune",
introduce() {
return "I am " + this.name + " from " + this.city;
},
};
const diya = {
name: "Diya",
city: "Guwahati",
introduce: aarav.introduce, // the very same function
};
console.log(aarav.introduce()); // I am Aarav from Pune
console.log(diya.introduce()); // I am Diya from Guwahati
// The function did not change. Only the speaker (the object before the dot) changed.
Both objects use the same function, yet the result is different, because the object before the dot is different. This is what lets one method work for thousands of objects.
this in a Normal Function Call
What if there is nothing before the dot? Then there is no speaker. In strict mode, this is undefined. Strict mode is a safer version of JavaScript that you switch on with "use strict", and it is always on inside classes and modules.
function whoIsSpeaking() {
"use strict";
return this;
}
console.log(whoIsSpeaking()); // undefined (no object before the dot)
const mic = { owner: "Anchor", whoIsSpeaking };
console.log(mic.whoIsSpeaking() === mic); // true
In old, non-strict scripts, this in a plain call falls back to the global object (window in a browser). That hides bugs, because your code silently reads and writes global variables. The strict behaviour is better: a mistake fails loudly.
Losing this in a Callback
This is the most common this bug. When you pass a method to another function, you pass only the function, not the object it came from. Later, the other function calls it with nothing before the dot, so this is lost. It is like recording Riya saying “I am the speaker” and playing the recording to a room that cannot see her.
class Speaker {
constructor(name) {
this.name = name;
}
introduce() {
console.log("I am " + this.name);
}
}
function passTheMic(callback) {
callback(); // called with nothing before the dot
}
const riya = new Speaker("Riya");
riya.introduce(); // I am Riya
passTheMic(riya.introduce); // ERROR: TypeError: Cannot read properties of undefined (reading 'name')
The error message says JavaScript tried to read name from undefined. That is your sign that this was lost. The same thing happens with setTimeout(riya.introduce, 1000) and addEventListener("click", riya.introduce). You will fix it in two ways below.
Arrow Functions Take this from Where They Are Written
Arrow functions follow a different rule. They do not have their own this. Inside an arrow function, this means whatever it means in the code around it, at the place where the arrow function is written. That makes arrow functions perfect for callbacks inside methods.
class Teacher {
constructor(name, students) {
this.name = name;
this.students = students;
}
callRoll() {
// An arrow function has no this of its own: it uses callRoll's this
this.students.forEach((student) => {
console.log(this.name + ": " + student + ", are you present?");
});
}
callRollWithNormalFunction() {
this.students.forEach(function (student) {
// A normal function gets its own this, and forEach gives it none
console.log(student + " is asked by", this);
});
}
}
const teacher = new Teacher("Mrs. Banerjee", ["Kabir", "Sana"]);
teacher.callRoll();
// Mrs. Banerjee: Kabir, are you present?
// Mrs. Banerjee: Sana, are you present?
teacher.callRollWithNormalFunction();
// Kabir is asked by undefined
// Sana is asked by undefined
| Kind of function | What decides this |
|---|---|
| Normal function or method | How it is called (the object before the dot) |
| Arrow function | Where it is written (the surrounding code) |
The flip side: do not use an arrow function as an object method. In { name: "Aarav", introduce: () => this.name }, the arrow function takes this from outside the object, so it never points to the object.
bind, call and apply
Sometimes you want to choose this yourself, like handing someone a script and saying “read this as Meera”. Every function has three methods for that:
fn.call(obj, a, b)runsfnnow, withthisset toobj.fn.apply(obj, [a, b])does the same, but takes the arguments as an array.fn.bind(obj)does not run anything. It returns a new function whosethisis fixed toobjforever, which is exactly what you need for callbacks.
function introduce(greeting, ending) {
return greeting + ", I am " + this.name + ending;
}
const meera = { name: "Meera" };
const kabir = { name: "Kabir" };
// call: run now, with this = meera, arguments one by one
console.log(introduce.call(meera, "Namaste", "!")); // Namaste, I am Meera!
// apply: run now, with this = kabir, arguments as an array
console.log(introduce.apply(kabir, ["Hello", "."])); // Hello, I am Kabir.
// bind: do not run now; return a new function with this fixed forever
const meeraSays = introduce.bind(meera, "Vanakkam");
console.log(meeraSays("!!")); // Vanakkam, I am Meera!!
// bind fixes the lost-this problem from before
class Speaker {
constructor(name) {
this.name = name;
}
introduce() {
console.log("I am " + this.name);
}
}
function passTheMic(callback) {
callback();
}
const riya = new Speaker("Riya");
passTheMic(riya.introduce.bind(riya)); // I am Riya
passTheMic(() => riya.introduce()); // I am Riya
The last two lines show the two standard fixes for a lost this: bind the method, or wrap the call in an arrow function so the method is called with the object before the dot.
this in Event Handlers
When the browser runs a normal function as an event handler, it sets this to the element the listener is attached to. An arrow function handler keeps the outer this instead. In both cases, event.currentTarget gives you the element reliably, which is why many developers prefer it. You learned about the event object in the JavaScript Events lesson.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>this in Event Handlers</title>
<style>
body { font-family: sans-serif; padding: 16px; }
button { padding: 10px 14px; margin: 4px; font-size: 16px; }
.picked { background: #ffd54f; }
</style>
</head>
<body>
<p>Pick your fest event:</p>
<button>Coding</button>
<button>Quiz</button>
<button>Dance</button>
<p id="out"></p>
<script>
const out = document.querySelector("#out");
document.querySelectorAll("button").forEach((button) => {
// A normal function: the browser sets this to the element
button.addEventListener("click", function () {
this.classList.toggle("picked");
console.log("this is the button:", this.textContent);
});
// An arrow function: this is NOT the button, so use event.currentTarget
button.addEventListener("click", (event) => {
console.log("this is the button?", this === event.currentTarget); // false
out.textContent = "Last clicked: " + event.currentTarget.textContent;
});
});
</script>
</body>
</html>
this in Classes
Inside class methods, this is the instance the method is called on, as you saw with the bank account. Classes run in strict mode, so a lost this gives undefined and a clear error. There are two common ways to make a class method safe to pass around:
- Bind it once in the constructor:
this.speak = this.speak.bind(this)or bind it where you add the listener. - Write it as an arrow function in a class field:
stop = () => { ... }. Each instance gets its own copy, withthisfixed to that instance.
Real-Life Example: A Debate Speaker Board
At an inter-college debate, each speaker gets a card on the board. Pressing “Give the mic” makes that speaker the current one and counts their turns. Each card is a Speaker object. Its click handler must know which speaker it belongs to, so this example uses both fixes: bind for speak and an arrow class field for stop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Debate Speaker Board</title>
<style>
body { font-family: sans-serif; padding: 16px; max-width: 420px; }
.speaker { display: flex; justify-content: space-between; align-items: center;
border: 1px solid #ccc; border-radius: 8px; padding: 8px 12px; margin: 8px 0; }
.speaking { background: #e3f2fd; border-color: #1e88e5; }
button { padding: 6px 10px; }
</style>
</head>
<body>
<h2>Inter-College Debate</h2>
<div id="board"></div>
<p id="now">Nobody is speaking.</p>
<script>
const board = document.querySelector("#board");
const now = document.querySelector("#now");
let current = null;
class Speaker {
constructor(name, college) {
this.name = name;
this.college = college;
this.turns = 0;
this.box = document.createElement("div");
this.box.className = "speaker";
this.label = document.createElement("span");
this.button = document.createElement("button");
this.button.textContent = "Give the mic";
this.box.append(this.label, this.button);
board.append(this.box);
// Option 1: bind once in the constructor
this.button.addEventListener("click", this.speak.bind(this));
this.update();
}
speak() {
if (current) {
current.stop();
}
current = this;
this.turns++;
this.box.classList.add("speaking");
now.textContent = "\"I am " + this.name + " from " + this.college + ", and I say...\"";
this.update();
}
// Option 2: an arrow function in a class field keeps this automatically
stop = () => {
this.box.classList.remove("speaking");
};
update() {
this.label.textContent = this.name + " (" + this.college + "), turns: " + this.turns;
}
}
new Speaker("Ishita", "St. Xavier's, Kolkata");
new Speaker("Farhan", "Govt. Arts College, Chennai");
new Speaker("Gurpreet", "DAV College, Chandigarh");
</script>
</body>
</html>
Try these changes:
- Remove
.bind(this)from the listener and click a button. The console shows an error, becausethisis now the button element, not the speaker. Put it back. - Add a fourth speaker from a college in your city.
- Add a “Time up” button to each card that calls
stopdirectly as the listener. It works without bind. Why?
Common Mistakes
- Passing a method as a callback, like
setTimeout(timer.start, 1000). The object is lost. Usetimer.start.bind(timer)or() => timer.start(). - Using an arrow function as an object method. It does not get the object as
this. Use the method shorthandintroduce() { ... }. - Using a normal function inside a method, for example in
forEachorsetTimeout. It gets its ownthis. Use an arrow function there. - Thinking
bindchanges the original function. It returns a new function. Save it, and use the same saved function if you later callremoveEventListener.
Practice Task: A Study Timer That Keeps Its this
Nisha wants a study timer that counts down and then tells her to take a break. Complete the StudyTimer class so that:
start()usessetIntervaland an arrow function, sothisstill points to the timer.- It logs the seconds left every second, then stops and logs “Break time, Nisha!”.
- You pass
timer.starttostartLaterwithout losingthis, once withbindand once with an arrow function.
class StudyTimer {
constructor(student, seconds) {
this.student = student;
this.secondsLeft = seconds;
}
start() {
// TODO 1: use setInterval to run once every second.
// Each time: reduce this.secondsLeft by 1 and log "Seconds left: <n>".
// When it reaches 0: stop the interval and log "Break time, <student>!"
// Hint: which kind of function keeps this pointing to the timer?
}
}
const timer = new StudyTimer("Nisha", 3);
// TODO 2: start the timer by passing timer.start to the helper below,
// without losing this. Try both bind and an arrow function.
function startLater(callback) {
setTimeout(callback, 500);
}
Show one possible answer (try it yourself first!)
class StudyTimer {
constructor(student, seconds) {
this.student = student;
this.secondsLeft = seconds;
}
start() {
const id = setInterval(() => {
this.secondsLeft--;
console.log("Seconds left: " + this.secondsLeft);
if (this.secondsLeft === 0) {
clearInterval(id);
console.log("Break time, " + this.student + "!");
}
}, 1000);
}
}
const timer = new StudyTimer("Nisha", 3);
function startLater(callback) {
setTimeout(callback, 500);
}
startLater(timer.start.bind(timer));
// or: startLater(() => timer.start());
// Seconds left: 2
// Seconds left: 1
// Seconds left: 0
// Break time, Nisha!
Key Takeaways
thisis like the word “I”: its meaning depends on who calls the function, not where it is defined.- In
obj.method(),thisisobj. In a plain call in strict mode, it isundefined. - Passing a method as a callback loses
this. Fix it withbindor an arrow function wrapper. - Arrow functions have no
thisof their own; they use thethisof the code around them. callandapplyrun a function with a chosenthis;bindreturns a new function withthisfixed.- In event handlers, prefer
event.currentTargetto find the element.