this is decided by how a function is called, not where it is written — except in arrow functions, which borrow it from their surroundings.
The four binding rules (+ arrows)
| Call style | this is | Priority |
|---|---|---|
new Fn() | The newly created object | 1 (highest) |
fn.call(obj), fn.apply(obj), fn.bind(obj)() | obj (explicit) | 2 |
obj.method() | obj (implicit — the object left of the dot) | 3 |
fn() plain call | undefined in strict mode, globalThis in sloppy mode | 4 (default) |
| Arrow function | this of the enclosing scope — never rebound | ignores all rules |
Implicit binding and how it is lost
const user = {
name: "Asha",
hello() { return `Hi, I'm ${this?.name}`; },
};
console.log(user.hello()); // this = user
const detached = user.hello; // just a function now
console.log(detached()); // plain call → this is undefined (strict)
const other = { name: "Ravi", hello: user.hello };
console.log(other.hello()); // this = otherHi, I'm Asha
Hi, I'm undefined
Hi, I'm Ravifunction strictThis() { return this; }
const sloppyThis = new Function("return this"); // Function-constructor code is sloppy
console.log(strictThis());
console.log(sloppyThis() === globalThis);
console.log(this); // top level of a moduleundefined
true
undefinedLost this in callbacks — and the fixes
class Timer {
constructor() { this.ticks = 0; }
startBroken() {
return [1, 2].map(function () { return this?.ticks; });
}
startArrow() {
return [1, 2].map(() => ++this.ticks); // arrow: this = the Timer
}
startBound() {
return [1, 2].map(function () { return this.ticks; }.bind(this));
}
}
const t = new Timer();
console.log(t.startBroken(), t.startArrow(), t.startBound());[ undefined, undefined ] [ 1, 2 ] [ 2, 2 ]Arrow functions as methods: a trap
const counter = {
count: 10,
arrow: () => typeof this, // this of the module scope → undefined
regular() { return this.count; },
};
console.log(counter.arrow(), counter.regular());undefined 10new binding
function Person(name) {
this.name = name; // `this` is the new object
}
const p = new Person("Mei");
console.log(p, p instanceof Person);Person { name: 'Mei' } trueHow is the value of this determined?
Why does passing obj.method as a callback lose this?