AV
HomeAboutProjectBlog

© 2026 Ave syah Shina. All rights reserved.

  1. Home
  2. Blog
  3. 13 — this — Four Binding Rules, and Why Arrow Functions Changed Everything

13 — this — Four Binding Rules, and Why Arrow Functions Changed Everything

August 13, 20266 min read
Download as Markdown

"Whatever object we're inside" was my this model, and it failed every time the call site changed. The idea that everything else hangs off: *this is not determined by where a function is defined — it's determined by how the function is called, and there are exactly four binding rules that decide its value.* [1]

The framing that finally landed is the four-rules view. Every function call resolves this through one of four mechanisms, checked in priority order. Once I could name which rule was in play, this stopped being a mystery and started being a lookup:

priority: low → high 1. Default plain call f() global, or undefined (strict) 2. Implicit method call obj.f() this = obj (the "before dot") 3. Explicit forced f.call(obj) this = obj (you set it) 4. New constructor new F() this = fresh empty object Arrow lexical () => {} inherits outer this; ignores all 4 rules

Rule 1 — Default binding

A plain function call — f() — with no object, no call, no new. In sloppy mode, this is the global object (window in browsers). In strict mode, this is undefined [1]. This is the lowest-priority rule; if any other rule applies, it overrides.

Rule 2 — Implicit binding

When a function is called as a property of an object — obj.method() — this inside method is obj, the object "before the dot" [2]. This is the most common and intuitive case:

const user = {
name: "Ave",
greet() { console.log(this.name); }
};
user.greet(); // "Ave" — this is user

The trap is losing implicit binding. If I peel the method off and call it bare, this reverts to default:

const fn = user.greet;
fn(); // undefined (or global) — no object before the dot

That's the classic "callback lost my this" bug — passing user.greet as a callback strips the receiver. The fixes are explicit binding (below) or arrow functions.

Rule 3 — Explicit binding: call, apply, bind

When I want to force this myself, three methods on Function.prototype do it [3][4]:

  • fn.call(thisArg, arg1, arg2) — call fn now, with this set to thisArg, arguments passed individually.
  • fn.apply(thisArg, [args]) — same, but arguments as an array.
  • fn.bind(thisArg, ...preset) — returns a new function with this permanently bound (and optional preset arguments). Doesn't call yet.

call/apply are for immediate invocation; bind is for creating a function with a fixed this to pass around. Function borrowing is the use case for call/apply — invoking one object's method against another object without copying it.

function greet(greeting) { return `${greeting}, ${this.name}`; }
greet.call({ name: "Ave" }, "Hi"); // "Hi, Ave"
const bound = greet.bind({ name: "Ave" }, "Hi");
bound(); // "Hi, Ave"

bind is the durable fix for the lost-this callback problem — setTimeout(user.greet.bind(user), 1000) keeps this as user no matter how the timer calls it.

Rule 4 — new binding

Calling a function with new creates a fresh empty object and sets this to it [1]. This is how constructor functions (pre-class) built instances, and it's the highest-priority rule — new overrides even explicit binding.

function User(name) { this.name = name; }
const u = new User("Ave"); // this = new object, returned automatically

The class syntax does the same thing under the hood — new is still the operator that triggers it.

Arrow functions: the escape hatch

Arrow functions ignore all four rules. They have no this of their own — this inside an arrow is whatever this was in the surrounding lexical scope, fixed at definition [5]. That's why arrows solved the callback problem: instead of remembering to .bind(this), I just write an arrow and this flows in from outside.

function Timer() {
this.seconds = 0;
setInterval(() => this.seconds++, 1000); // arrow inherits this = the Timer instance
}

The trade-off: arrows are wrong for object methods that need their receiver. const obj = { name: "Ave", greet: () => this.name } — this here is not obj, it's whatever the outer scope's this was. For methods, use regular functions or shorthand methods.

How I use this

The diagnostic whenever this is wrong is the same four questions: How was the function called? If it's a plain call, that's default binding (probably the bug). If it's obj.method(), implicit — but check whether the method got detached. If it's call/apply/bind, explicit. If it's new, new binding. And if it's an arrow, none of those — look one scope out. In practice, modern code leans on arrow functions for callbacks (inheriting this is usually what I want) and uses class with regular methods for objects that need a receiver. The four-rules vocabulary is what I reach for only when something's misbehaving — naming the rule pinpoints the fix.

References

[1] Mozilla, "this — JavaScript operator," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this

[2] I. Kantor, "Object methods, 'this'," The Modern JavaScript Tutorial, 2024. [Online]. Available: https://javascript.info/object-methods#this-in-methods

[3] Mozilla, "Function.prototype.call()," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call

[4] Mozilla, "Function.prototype.bind()," MDN Web Docs, 2024. [Online]. Available: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

[5] I. Kantor, "Function binding — arrows have no this," The Modern JavaScript Tutorial, 2024. [Online]. Available: https://javascript.info/bind

Knowledge check · Question 1 of 5

What determines the value of `this` inside a regular (non-arrow) function?

Comments

Leave a Comment

You must be signed in to comment

0 Comments

No comments yet. Be the first to comment!