The this Keyword in JavaScript: call vs apply vs bind
JavaScript sets this by how a function is called: arrow, new, call or bind, method call, or plain call. Why this is undefined in a callback, and three fixes.
- Course: JavaScript study plan
- Module: Functions, scope and closures
- Kind: Lesson
- Reading time: 15 min
- Runtime: Node 16
How does the this keyword work in JavaScript?
In JavaScript, this is decided by how a function is called, not where it is written. Five rules apply in order: an arrow function uses the enclosing scope's this; new binds a fresh object; call, apply and bind set it explicitly; obj.method() binds obj; and a plain call gives undefined in strict mode, or the global object in sloppy mode.
Lesson
this is the most misunderstood word in JavaScript because it is the one thing not decided lexically: its value depends on how a function is called, not where it was written — with the exception of arrow functions, which have no this of their own and borrow the enclosing one. There are exactly five rules, applied in order of precedence, and once you know them every "why is this undefined in my callback" has a one-line answer. This lesson states the rules, shows the classic loss of this, and covers the three methods — call, apply, bind — that set it explicitly.
The five rules, highest precedence first
- Arrow function — no own
this; uses thethisof the enclosing scope at the time the arrow was created. Nothing can change it, not evenbind. new—new F()creates a fresh object and bindsthisto it for the duration ofF.- Explicit binding —
f.call(obj, …),f.apply(obj, […]), and a function produced byf.bind(obj):thisisobj. - Method call —
obj.method():thisis the object before the dot (the receiver), whatever object that is at the call. - Plain call —
f():thisisundefinedin strict mode (the global object in sloppy mode).
"use strict";
const user = {
name: "Ada",
hello() { return `hi from ${this.name}`; },
later() { setTimeout(function () { console.log(this); }, 0); } // plain call inside → undefined
};
user.hello(); // "hi from Ada" — rule 4
const f = user.hello;
f(); // TypeError: Cannot read properties of undefined — rule 5
f.call({ name: "Bo" }); // "hi from Bo" — rule 3
The dot decides. Detach the method from the object and the receiver is gone.
Losing this, and the three fixes
The classic: passing a method as a callback.
class Counter {
count = 0;
increment() { this.count++; }
}
const c = new Counter();
setTimeout(c.increment, 0); // called as a plain function → this is undefined → TypeError
button.addEventListener("click", c.increment); // this would be the button, not c
Fixes, in order of preference:
setTimeout(() => c.increment(), 0); // 1. wrap in an arrow: the dot call is preserved
setTimeout(c.increment.bind(c), 0); // 2. bind: a new function with this fixed to c
class Counter { increment = () => { this.count++; }; } // 3. arrow class field: this is the instance forever
The arrow class field (rule 1 inside a constructor context) is the modern idiom for methods used as callbacks — React components used it for years. Its cost: one function object per instance instead of one shared method on the prototype.
call, apply, bind
function describe(prefix, suffix) { return `${prefix}${this.name}${suffix}`; }
const cat = { name: "Tom" };
describe.call(cat, "<", ">"); // "<Tom>" — arguments listed
describe.apply(cat, ["<", ">"]); // "<Tom>" — arguments as an array (spread made apply mostly obsolete)
const tagTom = describe.bind(cat, "["); // this = cat, prefix = "[" fixed
tagTom("]"); // "[Tom]"
bind returns a new function with this (and optionally leading arguments — partial application) permanently set; calling bind again on it does not rebind. call/apply invoke immediately. Modern uses: bind for callbacks and event handlers, call for borrowing methods (Array.prototype.slice.call(arguments) in old code; Object.prototype.hasOwnProperty.call(obj, key) still common), apply almost never now that Math.max(...arr) exists.
this in arrows, carefully
An arrow's this is whatever this was where the arrow was written:
const obj = {
name: "X",
regular() { return [1].map(function () { return this; }); }, // [undefined] — plain call inside map
arrow() { return [1].map(() => this.name); }, // ["X"] — arrow borrows obj's this
wrong: () => this, // arrow AT object-literal level: this is the module's this — not obj
};
Rule of thumb: arrows for callbacks inside methods; regular functions (or method shorthand) for the methods themselves. An arrow defined as an object-literal property does not see the object.
this at the top level and in modules
In a CommonJS file, top-level this is module.exports; in an ES module it is undefined; in a browser script it is window. Inside a plain function in strict mode it is undefined. None of these are useful — treat top-level this as something never to reference.
this in classes and constructors
function Person(name) { this.name = name; } // rule 2 with new: this is the new object
const p = new Person("Ada");
Person("Bo"); // rule 5 without new: TypeError in strict mode (this is undefined)
class Point { constructor(x, y) { this.x = x; this.y = y; } dist() { return Math.hypot(this.x, this.y); } }
Class constructors throw if called without new — one of the reasons class replaced constructor functions. Inside methods, rule 4 applies as usual, and losing this when passing p.dist around is the same problem with the same three fixes.
globalThis, and why this is not for globals
If you need the global object, write globalThis; never this at top level (it differs by environment). The sloppy-mode rule that made plain-call this the global object is the reason old code accidentally created global variables from inside functions.
A method for reading any this
- Is the function an arrow? Take the enclosing scope's
thisat definition. - Was it called with
new? The new object. call/apply/bind? The given object.- Called as
something.f()?something. - Otherwise
undefined(strict).
Run the list on any snippet and the answer falls out.
Interview angle
- "How is
thisdetermined?" By the call:new→ new object; explicitcall/apply/bind→ given object; method call → receiver; plain call →undefinedin strict mode; arrows inherit lexically and cannot be rebound. - "Why is
thisundefined in my callback?" The method was passed and later called as a plain function; wrap in an arrow,bindit, or use an arrow class field. - "
callversusapplyversusbind?" Immediate with listed args; immediate with an array; returns a new bound function. - "Can you
bindan arrow function?" No effect onthis— arrows have none of their own. - "What is
thisin an arrow defined inside a method?" The method'sthis— that is the whole point of using an arrow there.
Key takeaways
- Five rules by precedence: arrow (lexical) →
new→call/apply/bind→ receiver of the dot →undefined. - Detaching a method loses its receiver; fix with an arrow wrapper,
bind, or an arrow class field. bindfixesthis(and leading arguments) permanently;call/applyinvoke now.- Arrows inside methods are right; arrows as methods are wrong.
- Never rely on top-level
this; useglobalThisfor the global object.
Common questions
What is the difference between call, apply and bind in JavaScript?
call invokes the function now with a given this and the arguments listed one by one; apply does the same with the arguments as an array; bind invokes nothing and returns a new function whose this, and optionally leading arguments, are fixed permanently.
Why is this undefined in my callback?
The method was passed on its own, for example setTimeout(obj.method, 0), and later called as a plain function, so there is no receiver and strict mode gives undefined. Wrap it in an arrow, () => obj.method(), bind it with obj.method.bind(obj), or define it as an arrow class field.
Can you bind an arrow function in JavaScript?
You can call bind on an arrow, but it has no effect on this: an arrow has no this of its own and always uses the one from the scope where it was created. bind can still fix its leading arguments.
Should you use arrow functions as object methods?
No. An arrow written as an object-literal property takes this from the surrounding scope, not from the object, so this.name does not find the object's field. Use method shorthand for methods and arrows for the callbacks inside them.
What is this at the top level in Node.js?
In a CommonJS file top-level this is module.exports; in an ES module it is undefined; in a browser script it is window. Because it differs by environment, never rely on it, and write globalThis when you need the global object.
Exercises
Which rule decided?
Given ada = { name: "Ada", who() {…}, viaArrow() {…} } (who returns this.name, or "undefined" when this is undefined; viaArrow maps over [1] with an arrow returning this.name), bo = { name: "Bo" } and a constructor Thing(name), read n call kinds and print <kind>: <result>: method (ada.who()), detached (const f = ada.who; f()), bound (ada.who.bind(bo)()), call (ada.who.call({ name: "Cy" })), arrow-in-method (ada.viaArrow()), new (new Thing("instance").name).
Example: 3 then method detached bound →
method: Ada
detached: undefined
bound: BoFix the callback, three ways
A Counter class has count, a method increment() and an arrow field incrementArrow. Read n. Register n callbacks in a later array in four ways — the broken detached method broken.increment, an arrow wrapper () => wrapped.increment(), a bound copy bound.increment.bind(bound), and the arrow field field.incrementArrow — each on its own Counter instance. Then run every callback inside try/catch, counting the errors, and print broken=<count> errors=<n> and wrapped=<> bound=<> field=<>.
Example: 3 →
broken=0 errors=3
wrapped=3 bound=3 field=3In this module: Functions, scope and closures
- Declaring functions — declarations, expressions, arrows and parameters
- Lexical scope, the scope chain and hoisting
- Closures — functions that remember
- this — the five binding rules, call, apply and bind (this lesson)
- Higher-order functions — composition, currying and callbacks
- Checkpoint — Functions, scope and closures
← Closures — functions that remember · Higher-order functions — composition, currying and callbacks →