Unlike most variables, this isn't determined by where a regular function is defined — it's determined by how the function is called.
const obj = {
name: "Alice",
greet() {
console.log(this.name);
},
};
obj.greet(); // "Alice" — called as obj.greet(), so this = obj
const fn = obj.greet;
fn(); // undefined (or TypeError in strict mode) — called plainly, this is not obj anymore
fn() calls the exact same function as obj.greet(), but this binding depends on the call-site syntax (obj.greet() vs. plain fn()), not on where the function was written. This is exactly why passing a method as a callback (onClick={obj.greet}) silently breaks its this binding — the function is being called plainly by whatever invokes the callback, detached from obj.
this gets determined| Call pattern | this inside the function |
|---|---|
obj.method() | obj — the object the method was called on |
fn() (plain call) | undefined in strict mode / classes; the global object in loose, non-strict mode |
fn.call(thisArg, ...args) / fn.apply(thisArg, argsArray) | Explicitly thisArg, for that one call |
new Fn() | The newly created object |
| Arrow function | Not its own — inherited lexically from the enclosing scope at definition time |
Arrow functions don't have their own this binding at all — they capture this lexically from the surrounding scope at the time they're defined, exactly like a closure captures a normal variable. This is exactly why arrow functions are so commonly used for callbacks inside methods:
const timer = {
seconds: 0,
start() {
setInterval(() => {
this.seconds++; // arrow function — `this` is still `timer`, inherited from start()
}, 1000);
},
};
const brokenTimer = {
seconds: 0,
start() {
setInterval(function () {
this.seconds++; // regular function — `this` here is NOT brokenTimer (undefined or the global object)
}, 1000);
},
};
Because an arrow function has no this of its own, call/apply/bind cannot change it — they're simply ignored for an arrow function's this.
call, apply, and bindAll three exist to explicitly control what this a function runs with, rather than relying on call-site syntax.
function introduce(greeting) {
console.log(`${greeting}, I'm ${this.name}`);
}
const person = { name: "Sam" };
introduce.call(person, "Hi"); // "Hi, I'm Sam" — calls immediately, args listed individually
introduce.apply(person, ["Hi"]); // "Hi, I'm Sam" — calls immediately, args as an array
const boundIntroduce = introduce.bind(person);
boundIntroduce("Hello"); // "Hello, I'm Sam" — returns a NEW function, permanently bound, called later
bind is the one used most often in practice, because it produces a reusable function rather than invoking anything immediately — the standard fix for passing a class method as an event handler without losing its this:
class Toggle {
constructor() {
this.on = false;
this.handleClick = this.handleClick.bind(this); // bind once, in the constructor
}
handleClick() {
this.on = !this.on;
}
}