this Binding Rules
How JavaScript decides this — default, method, explicit call/apply/bind, new, and lexical this in arrows. With runnable examples.
- 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 nowapply(thisArg, argsArray)— invoke now with array of argsbind(thisArg, ...partialArgs)— return a new function withthisfixed
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:
thisis the global object (windowin browsers — dangerous). - Strict mode (modules are strict by default):
thisisundefined.
'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
thiswithcall/apply/bindin a meaningful way forthis. - 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.
Related on this site
- Event Delegation — handlers and targets
- Closures — lexical environment (arrows share the story)
- The Event Loop — when callbacks actually run
- call / apply / bind — deeper explicit binding
- JavaScript Interview Guide
Further reading
- MDN: this
- MDN: Function.prototype.bind
- javascript.info: Object methods, “this”
- ECMAScript: this binding
Related guides
- call, apply, and bindExplicit this control — call vs apply vs bind, partial application, bound constructors, and when arrows make bind pointless.
- Arrow Functions Deep DiveLexical this, no arguments object, no construct, concise bodies — when arrows help and when methods and generators need classic functions.
- AbortControllerCancel fetch and other async work with AbortController and AbortSignal — timeouts, race conditions, and cleanup when components unmount.
- Array find, some, every, includesShort-circuiting array predicates: find, findIndex, some, every, and includes — when to use each and common interview traps.
- Array map, filter, reducemap, filter, and reduce as the core transform toolkit — immutability, chaining costs, reduce patterns, and when a plain loop is clearer.