---
title: "13 — this — Four Binding Rules, and Why Arrow Functions Changed Everything"
uid: this-keyword
tags: ["this", "binding", "arrow-functions", "roadmap:javascript", "bind", "apply", "call", "javascript"]
excerpt: "this isn't where a function is defined — it's how it's called. Four binding rules decide it, and arrow functions bypass all of them by inheriting lexically."
date: 2026-08-13T03:28:05+0000
source: https://www.aveshina.my.id/en/blog/this-keyword
---

"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:

```figure
<svg viewBox="0 0 740 290" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="Four binding rules for this, in priority order from lowest to highest: Default binding (global or undefined in strict), Implicit binding (obj.method()), Explicit binding (call/apply/bind), New binding (new Constructor). A fifth path: arrow functions inherit this lexically and ignore all four.">
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">
    <text x="370" y="22" font-size="10" font-weight="700" fill="#475569" text-anchor="middle">priority: low → high</text>

    <!-- Default -->
    <rect x="20" y="40" width="135" height="180" rx="8" fill="#fee2e2" stroke="#dc2626" stroke-width="1.5"/>
    <text x="87" y="65" font-size="11" font-weight="700" fill="#7f1d1d" text-anchor="middle">1. Default</text>
    <text x="87" y="90" font-size="10" fill="#7f1d1d" text-anchor="middle">plain call</text>
    <text x="87" y="106" font-size="9.5" fill="#7f1d1d" text-anchor="middle">f()</text>
    <text x="87" y="160" font-size="9" font-style="italic" fill="#7f1d1d" text-anchor="middle">global, or</text>
    <text x="87" y="174" font-size="9" font-style="italic" fill="#7f1d1d" text-anchor="middle">undefined (strict)</text>

    <!-- Implicit -->
    <rect x="170" y="40" width="135" height="180" rx="8" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="237" y="65" font-size="11" font-weight="700" fill="#1e1b4b" text-anchor="middle">2. Implicit</text>
    <text x="237" y="90" font-size="10" fill="#1e1b4b" text-anchor="middle">method call</text>
    <text x="237" y="106" font-size="9.5" font-family="ui-monospace,monospace" fill="#1e1b4b" text-anchor="middle">obj.f()</text>
    <text x="237" y="160" font-size="9" font-style="italic" fill="#1e1b4b" text-anchor="middle">this = obj</text>
    <text x="237" y="174" font-size="9" font-style="italic" fill="#1e1b4b" text-anchor="middle">(the "before dot")</text>

    <!-- Explicit -->
    <rect x="320" y="40" width="135" height="180" rx="8" fill="#dcfce7" stroke="#16a34a" stroke-width="1.5"/>
    <text x="387" y="65" font-size="11" font-weight="700" fill="#052e16" text-anchor="middle">3. Explicit</text>
    <text x="387" y="90" font-size="10" fill="#052e16" text-anchor="middle">forced</text>
    <text x="387" y="106" font-size="9.5" font-family="ui-monospace,monospace" fill="#052e16" text-anchor="middle">f.call(obj)</text>
    <text x="387" y="160" font-size="9" font-style="italic" fill="#052e16" text-anchor="middle">this = obj</text>
    <text x="387" y="174" font-size="9" font-style="italic" fill="#052e16" text-anchor="middle">(you set it)</text>

    <!-- New -->
    <rect x="470" y="40" width="135" height="180" rx="8" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.5"/>
    <text x="537" y="65" font-size="11" font-weight="700" fill="#422006" text-anchor="middle">4. New</text>
    <text x="537" y="90" font-size="10" fill="#422006" text-anchor="middle">constructor</text>
    <text x="537" y="106" font-size="9.5" font-family="ui-monospace,monospace" fill="#422006" text-anchor="middle">new F()</text>
    <text x="537" y="160" font-size="9" font-style="italic" fill="#422006" text-anchor="middle">this = fresh</text>
    <text x="537" y="174" font-size="9" font-style="italic" fill="#422006" text-anchor="middle">empty object</text>

    <!-- Arrow -->
    <rect x="620" y="40" width="110" height="180" rx="8" fill="#fce7f3" stroke="#db2777" stroke-width="1.5"/>
    <text x="675" y="65" font-size="11" font-weight="700" fill="#500724" text-anchor="middle">Arrow</text>
    <text x="675" y="90" font-size="10" fill="#500724" text-anchor="middle">lexical</text>
    <text x="675" y="106" font-size="9.5" font-family="ui-monospace,monospace" fill="#500724" text-anchor="middle">() =&gt; {}</text>
    <text x="675" y="158" font-size="9" font-style="italic" fill="#500724" text-anchor="middle">inherits outer</text>
    <text x="675" y="172" font-size="9" font-style="italic" fill="#500724" text-anchor="middle">this; ignores</text>
    <text x="675" y="186" font-size="9" font-style="italic" fill="#500724" text-anchor="middle">all 4 rules</text>
  </g>
</svg>
```

## 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](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](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](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](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](https://javascript.info/bind)

```quiz
Q: What determines the value of `this` inside a regular (non-arrow) function?
- Where the function is defined
- How the function is called (the four binding rules)
correct: 1
explain: this is a runtime binding decided by the call site. A function defined inside an object can still have this = global or undefined if it's called as a bare function. Definition location is irrelevant for regular functions.

Q: `const fn = obj.method; fn();` — what is this inside fn?
- obj (the method remembers where it came from)
- undefined (in strict mode) or the global object — implicit binding is lost because there's no object before the dot
correct: 1
explain: Implicit binding depends on the call form obj.method(). Peeling the method off and calling it bare reverts to default binding. This is the classic lost-this bug in callbacks.

Q: Which method creates a new function with a permanently fixed this?
- fn.call(thisArg, args)
- fn.bind(thisArg)
correct: 1
explain: bind returns a new function whose this is locked in. call/apply invoke immediately. bind is the durable fix for callbacks that lose this.

Q: Arrow functions and this — which is true?
- Arrows have no own this; they inherit it from the surrounding lexical scope, ignoring all four binding rules
- Arrows follow the same four binding rules as regular functions
correct: 0
explain: Arrows are defined with the this of their enclosing scope, fixed at creation. call/apply/bind cannot change an arrow's this. This makes them ideal for callbacks where you want outer this to flow in.

Q: Why is an arrow function the wrong choice for an object method that needs the receiver?
- The method would be slow
- The arrow inherits this from the outer scope, not the object it's a property of — this won't be the receiver
correct: 1
explain: const obj = { greet: () => this.name } — this inside the arrow is the enclosing scope's this, NOT obj. For methods that need this = the receiver, use a regular function or shorthand method syntax.
```
