this Keyword in JavaScript: Call-Site Rules with Worked Examples

Trace JavaScript receivers through ordinary functions, arrows, callbacks, constructors, call, and bind. Worked examples turn this from a guess into a call-site rule.

KnowledgeGate Team

Exam prep & CS education

Updated 15 Sep 20265 min read

The JavaScript this keyword does not mean "the object where the function was written". An ordinary function usually gets its receiver from the call site; an arrow inherits this from its surrounding scope. Strict-mode traces are more reliable than loose definitions. Use the broader programming tutorial path and explore the free coding courses.

What this means when a function runs

this is the receiver supplied to the current invocation. An ordinary function can receive different values at different call sites. An arrow has no this of its own and closes over the surrounding value.

Snippets use strict mode. A plain ordinary-function call then supplies undefined as this. Non-strict scripts and host environments can differ, so "it is always window" is unsafe.

js
'use strict';

const box = {
  value: 6,
  read() { return this.value; }
};

box.read();          // 6
const read = box.read;
read();              // TypeError

box.read() selects box and returns 6. Detached read() is a plain call: this is undefined, so reading .value throws a TypeError.

Five call patterns that decide the receiver

Read the syntax immediately left of the parentheses.

Call expression

Rule

Receiver

Result

read()

Plain call

undefined

TypeError on .value

box.read()

Implicit method call

box

6

box.read.call({ value: 9 })

Explicit call

Supplied object

9

new Meter(7)

Constructor call

New instance

value becomes 7

box.read.bind(box)

Bound function creation

Permanently box for ordinary calls

Later call returns 6

The constructor row uses:

js
'use strict';

function Meter(start) { this.value = start; }
const meter = new Meter(7);

new creates an object, supplies it as this, assigns meter.value = 7, and returns it because the constructor returns no other object.

A bound function ignores a later .call(...) receiver. If it is constructable and invoked with new, however, the fresh object becomes this.

Worked example: one account through five call sites

Follow this program in order:

js
'use strict';

const account = {
  owner: 'Asha',
  balance: 1200,
  deposit(amount) {
    this.balance += amount;
    return `${this.owner}:${this.balance}`;
  }
};

account.deposit(300);                 // 'Asha:1500'
const detached = account.deposit;
detached(200);                        // TypeError; balance stays 1500
detached.call(account, 200);          // 'Asha:1700'
const boundDeposit = detached.bind(account);
boundDeposit(100);                    // 'Asha:1800'
boundDeposit.call({ owner: 'Dev', balance: 5 }, 50);
                                      // 'Asha:1850'

Trace expression, receiver, old balance, amount, and result:

Expression

Selected receiver

Old balance

Amount

Result

account.deposit(300)

account

1200

300

Asha:1500

detached(200)

undefined

account stays at 1500

200

TypeError, no mutation

detached.call(account, 200)

account

1500

200

Asha:1700

boundDeposit(100)

bound account

1700

100

Asha:1800

boundDeposit.call({ owner: 'Dev', balance: 5 }, 50)

bound account

1800

50

Asha:1850

The failure does not change account.balance, so the next call starts at 1500, not 1700. Debug by asking, "What exact expression invoked this function?" not only where it was declared.

Timeline of the account example showing how each call site selects its receiver and moves the balance from 1500 to 1850.

Arrow functions, callbacks, and object-literal traps

Compare regular and arrow callbacks:

js
'use strict';

const scoreboard = {
  points: 10,
  bonuses: [2, 5],
  totalsWrong() {
    return this.bonuses.map(function (bonus) {
      return this.points + bonus;
    });
  },
  totalsRight() {
    return this.bonuses.map((bonus) => this.points + bonus);
  }
};

totalsWrong() throws because map invokes the regular callback without scoreboard. totalsRight() returns [12, 15]: its arrow inherits this and computes 10 + 2 = 12, then 10 + 5 = 15.

A regular callback works when map receives a thisArg:

js
return this.bonuses.map(function (bonus) {
  return this.points + bonus;
}, this); // [12, 15]

An arrow is not an object-method shortcut:

js
function makeCard() {
  'use strict';
  return {
    label: 'JS',
    regular() { return this.label; },
    arrow: () => this?.label
  };
}

const card = makeCard();
card.regular(); // 'JS'
card.arrow();   // undefined

The regular method receives card. The arrow captured the factory's strict-mode undefined, not card. Retaining context is useful when you move from JavaScript callbacks into component state.

Two callback lanes: a regular map callback that throws versus an arrow callback that inherits scoreboard and returns [12, 15].

Common this mistakes and their repairs

A detached or destructured method loses its receiver: const { deposit } = account; deposit(25) throws. Repair it with const depositForAsha = account.deposit.bind(account) or (amount) => account.deposit(amount). The fix preserves a call site, not a variable name.

Method callbacks have the same problem:

js
'use strict';

const wallet = {
  balance: 40,
  add(x) { this.balance += x; return this.balance; }
};
const invoke = (fn) => fn(25);

invoke(wallet.add);                    // TypeError
invoke((amount) => wallet.add(amount)); // 65

The repair leaves wallet.balance === 65. Use arrows to retain a surrounding receiver, not where callers must select one with method syntax, .call, .apply, or .bind.

How tracing and interview questions test this

This is common JavaScript tracing and interview practice, not an official pattern for a named exam. Connect it with callbacks during web-technology revision. Named exam patterns require that exam's current official notice.

js
'use strict';

const lookup = { value: 4, get() { return this.value; } };
const other = { value: 9 };
lookup.get.call(other);              // 9
const fixed = lookup.get.bind(lookup);
fixed.call(other);                   // 4

The first call selects other. Binding then fixes lookup, so .call(other) cannot replace it. Solve in three passes: mark arrows, classify other calls as plain, method, explicit, constructor, or bound, then compute. Name the receiver before the output.

Three exercises with answers

  1. const team = { score: 8, add(x) { return this.score + x; } }; const add = team.add.bind({ score: 20 }); add(3);

    Answer: 23. The bound receiver has score = 20; team.score is irrelevant.

  2. const box = { base: 6, nums: [1, 4], totals() { return this.nums.map(function (n) { return this.base + n; }); } };

    Answer: Change the callback to (n) => this.base + n. Then box.totals() returns [7, 10], from 6 + 1 and 6 + 4.

  3. function Point(x, y) { this.x = x; this.y = y; } const p = new Point(2, 5); p.x + p.y;

    Answer: 7. new creates p, supplies it as this, and stores 2 and 5 on the object.

Short version and the next practical step

  • Ordinary functions inspect the call site.

  • Arrows inherit this from the surrounding scope.

  • Detached methods lose their receiver.

  • .call and .apply supply one call's receiver.

  • .bind creates a reusable bound function. With a constructable bound function, new still constructs a fresh receiver.

Rerun the account example with a starting balance of 2400 and a first deposit of 600. The successful balances become 3000, 3200, 3300, and 3350; the detached call still fails and changes nothing.

Now practise these rules in a complete JavaScript learning path. Predict each receiver on paper, run the snippet, then explain every mismatch from the call site.