AI InterviewSearch…Search problems, topics, or pages…Ctrl KGet started
Skip to content

03 Functions, scope and closures / Lesson 4 of 5

this — the five binding rules, call, apply and bind

15 min · +10 XP · 2 exercises · 5 questions

Read the text15 min
Exercises0/2
Quiz5 q

Quick answer

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.

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

  1. Arrow function — no own this; uses the this of the enclosing scope at the time the arrow was created. Nothing can change it, not even bind.
  2. new — new F() creates a fresh object and binds this to it for the duration of F.
  3. Explicit binding — f.call(obj, …), f.apply(obj, […]), and a function produced by f.bind(obj): this is obj.
  4. Method call — obj.method(): this is the object before the dot (the receiver), whatever object that is at the call.
  5. Plain call — f(): this is undefined in 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

  1. Is the function an arrow? Take the enclosing scope's this at definition.
  2. Was it called with new? The new object.
  3. call/apply/bind? The given object.
  4. Called as something.f()? something.
  5. Otherwise undefined (strict).

Run the list on any snippet and the answer falls out.

Interview angle

  • "How is this determined?" By the call: new → new object; explicit call/apply/bind → given object; method call → receiver; plain call → undefined in strict mode; arrows inherit lexically and cannot be rebound.
  • "Why is this undefined in my callback?" The method was passed and later called as a plain function; wrap in an arrow, bind it, or use an arrow class field.
  • "call versus apply versus bind?" Immediate with listed args; immediate with an array; returns a new bound function.
  • "Can you bind an arrow function?" No effect on this — arrows have none of their own.
  • "What is this in an arrow defined inside a method?" The method's this — 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.
  • bind fixes this (and leading arguments) permanently; call/apply invoke now.
  • Arrows inside methods are right; arrows as methods are wrong.
  • Never rely on top-level this; use globalThis for 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.

Mark as readSign in to keep your place.

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

  1. Arrow function — no own this; uses the this of the enclosing scope at the time the arrow was created. Nothing can change it, not even bind.
  2. new — new F() creates a fresh object and binds this to it for the duration of F.
  3. Explicit binding — f.call(obj, …), f.apply(obj, […]), and a function produced by f.bind(obj): this is obj.
  4. Method call — obj.method(): this is the object before the dot (the receiver), whatever object that is at the call.
  5. Plain call — f(): this is undefined in 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

  1. Is the function an arrow? Take the enclosing scope's this at definition.
  2. Was it called with new? The new object.
  3. call/apply/bind? The given object.
  4. Called as something.f()? something.
  5. Otherwise undefined (strict).

Run the list on any snippet and the answer falls out.

Interview angle

  • "How is this determined?" By the call: new → new object; explicit call/apply/bind → given object; method call → receiver; plain call → undefined in strict mode; arrows inherit lexically and cannot be rebound.
  • "Why is this undefined in my callback?" The method was passed and later called as a plain function; wrap in an arrow, bind it, or use an arrow class field.
  • "call versus apply versus bind?" Immediate with listed args; immediate with an array; returns a new bound function.
  • "Can you bind an arrow function?" No effect on this — arrows have none of their own.
  • "What is this in an arrow defined inside a method?" The method's this — 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.
  • bind fixes this (and leading arguments) permanently; call/apply invoke now.
  • Arrows inside methods are right; arrows as methods are wrong.
  • Never rely on top-level this; use globalThis for 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: Bo

Fix 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=3

In this module: Functions, scope and closures

← Closures — functions that remember · Higher-order functions — composition, currying and callbacks →