Q: How does the this keyword work in JavaScript? Explain all the binding rules.

Answer:

this is not a reference to the function itself or to where it was defined. It is determined by how a function is called (call-site), with four classic rules plus arrow-function lexical binding. Strict mode and ES modules also affect the defaults.

The Four Classic Rules (in precedence order)

  1. new bindingnew Foo() creates a new object and binds this to it.
  2. Explicit bindingfn.call(obj), fn.apply(obj), fn.bind(obj).
  3. Implicit bindingobj.fn() binds this to obj.
  4. Default binding — bare fn() call. In strict mode this is undefined; in sloppy mode it's the global object (window / globalThis).

Arrow functions ignore all four rules: they capture this from the enclosing lexical scope at definition time.

Examples for Each Rule

'use strict';

function whoAmI() { return this; }

// 4. Default
whoAmI();                    // undefined (strict) / window (sloppy)

// 3. Implicit
const obj = { whoAmI };
obj.whoAmI();                // obj

// 2. Explicit
whoAmI.call({ name: 'X' });  // { name: 'X' }

// 1. new
function Person(name) { this.name = name; }
const p = new Person('Ada'); // this -> brand new object

The "Lost this" Pitfall

const user = {
  name: 'Ada',
  greet() { console.log(`Hi, ${this.name}`); },
};

const greet = user.greet;
greet();                          // Hi, undefined  (default binding!)
setTimeout(user.greet, 100);      // same problem — method passed as bare reference

The reference to the function was extracted from user, so the call-site is now plain greet(). Fixes:

setTimeout(user.greet.bind(user), 100);
setTimeout(() => user.greet(), 100); // arrow keeps `user.greet()` as the call-site

Arrow Functions

Arrows don't have their own this, arguments, super, or new.target. They inherit this from the surrounding scope at creation time — bind/call/apply cannot change it.

class Timer {
  constructor() {
    this.count = 0;
    setInterval(() => { this.count++; }, 1000); // `this` is the instance
  }
}

If you used a function expression here, the interval callback would have this === undefined (strict) or window.

Precedence Quick Test

function Foo() { this.a = 1; }
const obj = {};
const bound = Foo.bind(obj);
new bound();              // {a: 1}  — `new` wins over bind
console.log(obj.a);       // undefined

[!NOTE] new beats explicit binding. This is the only case where bind can be overridden.

Method Shorthand vs Property Arrow in Classes

class Btn {
  // Prototype method — `this` is whoever calls it
  click() { console.log(this); }

  // Class field arrow — `this` is the instance permanently
  onClick = () => console.log(this);
}

Use the arrow-field form for event handlers passed to React/DOM to avoid manual .bind.

Modules and this

At the top level of an ES module, this is undefined. In a CommonJS module, top-level this equals module.exports. In a browser <script> without type=module, top-level this is window.