ESC

Type to search the knowledge base.

this Binding Rules

How JavaScript decides this — default, method, explicit call/apply/bind, new, and lexical this in arrows. With runnable examples.

intermediate4 min read
  • javascript
  • this
  • call
  • apply
  • bind

this is not “the object the function is defined on.” It is decided almost entirely by how the function is called (for ordinary functions), or by where it was created (for arrow functions).

If you treat this as lexical for every function, class methods and DOM handlers will surprise you. If you ignore arrows, you’ll over-bind everything.

Primary refs: MDN this, javascript.info — object methods.

The four ordinary-function rules (call-site)

Evaluate in this priority order when the function is a non-arrow function:

1. new binding

function Person(name) {
  this.name = name;
}
const p = new Person('Ada');
// this === new object being constructed

new creates an object, sets this to it, links the prototype, returns the object (unless you return another object explicitly).

2. Explicit binding — call, apply, bind

function greet(greeting) {
  return `${greeting}, ${this.name}`;
}

const user = { name: 'Grace' };

greet.call(user, 'Hello');   // "Hello, Grace"
greet.apply(user, ['Hi']);   // "Hi, Grace"

const bound = greet.bind(user, 'Hey');
bound(); // "Hey, Grace"
  • call(thisArg, ...args) — invoke now
  • apply(thisArg, argsArray) — invoke now with array of args
  • bind(thisArg, ...partialArgs) — return a new function with this fixed

bind wins against later call/apply on the bound function (except new on a bound constructor, which is a rare edge).

3. Method / implicit binding

const counter = {
  n: 0,
  inc() {
    this.n += 1;
    return this.n;
  },
};

counter.inc(); // 1 — this === counter

The call looks like obj.method(). The base object (counter) becomes this.

The detachment bug:

const fn = counter.inc;
fn(); // this is not counter

const { inc } = counter;
inc(); // same problem

Passing a method as a callback detaches it:

// setTimeout(counter.inc, 0) — loses counter
setTimeout(() => counter.inc(), 0); // OK
setTimeout(counter.inc.bind(counter), 0); // OK

4. Default binding

Bare call: fn().

  • Non-strict / sloppy: this is the global object (window in browsers — dangerous).
  • Strict mode (modules are strict by default): this is undefined.
'use strict';
function show() {
  console.log(this);
}
show(); // undefined

Always write libraries as if default this is undefined.

Arrow functions: lexical this

Arrows do not get their own this. They close over this from the enclosing scope at creation time.

const timer = {
  label: 'tick',
  start() {
    setInterval(() => {
      console.log(this.label); // this === timer (from start)
    }, 1000);
  },
};

Compare with a normal function callback:

setInterval(function () {
  console.log(this.label); // this is not timer (default / weird in timers)
}, 1000);

Consequences:

  • You cannot re-bind an arrow’s this with call/apply/bind in a meaningful way for this.
  • Don’t use arrows for object methods when you want dynamic this:
const bad = {
  n: 1,
  // lexical this is outer scope (module), not bad
  inc: () => {
    this.n += 1;
  },
};
  • Class fields as arrows capture the instance — useful for React class handlers historically; methods on the prototype stay shared and cheaper.

Classes and this

class Button {
  constructor(label) {
    this.label = label;
  }
  click() {
    console.log(this.label);
  }
}

const b = new Button('Save');
b.click(); // "Save"

const handler = b.click;
handler(); // TypeError or undefined this — method extracted

In React function components you rarely touch this. In older class components, this.handleClick = this.handleClick.bind(this) (or public field arrows) was the standard fix.

DOM handlers

button.addEventListener('click', function () {
  // this === button (the element the listener is on, for non-arrow)
});

button.addEventListener('click', () => {
  // this === lexical outer this, NOT the button
});

Prefer event.currentTarget when you care about the element, not this — clearer with arrows.

Quick decision table

Call form this (ordinary fn)
new Fn() new instance
fn.call(obj) / apply / bound fn obj (explicit)
obj.fn() obj
fn() undefined (strict) / global (sloppy)
Arrow enclosing lexical this

Interview angle

Walk this snippet out loud:

const obj = {
  x: 10,
  getX() {
    return this.x;
  },
  getXArrow: () => this.x,
};

const g = obj.getX;
console.log(obj.getX());     // 10
console.log(g());            // undefined / error depending on mode
console.log(obj.getXArrow()); // outer this.x — not 10 from obj

Name call-site rules, then arrows are lexical. Mention bind for callbacks and why method extraction breaks.

Further reading

Related guides