How is the this binding decided in JavaScript?
The binding is decided by how the function is called, not by where it was written. There are four rules, and they are checked in a fixed order.
// 1. Default binding - a plain call with no receiver.
function plain() { return this === globalThis; }
console.log(plain()); // true in sloppy mode
// 2. Implicit binding - the object before the dot.
const obj = {
name: "obj",
method() { return this.name; },
};
console.log(obj.method()); // "obj"
// 3. Explicit binding - call, apply and bind.
console.log(obj.method.call({ name: "other" })); // "other"
// 4. new binding - the newly created object.
function Point(x) { this.x = x; }
console.log(new Point(3).x); // 3
// Arrow functions inherit this from the scope they were written in.
const outerThis = this;
const arrow = () => this === outerThis;
console.log(arrow()); // true - the arrow kept the surrounding thisThe order matters. If you call a function with the new keyword, the new binding applies. Otherwise, if you used call, apply or bind, the explicit binding applies. Otherwise, if the call had a receiver, the implicit binding applies. Otherwise you fall back to the default binding, which is the global object in sloppy mode and no value at all in strict mode.
The default binding is the one that causes the most confusion, because it changes depending on the mode of the file. The same function called with no receiver gets the global object in sloppy code and nothing in strict code, so code that relies on it can work in one build and break in another.
The arrow function line is the exception to the whole model. An arrow function has no binding of its own, so none of the four rules apply to it. It inherits the this of the scope it was written in, at the moment it was created.