this, call() & bind()

The Course
•
4 min read

this is generally determined by the call, not by where an ordinary function was written. In counter.add(), counter is the receiver. A detached call such as const add = counter.add; add() loses that receiver. ES modules run in strict mode; detached ordinary functions receive undefined as this. This JavaScript editor starts with a classic script, so the example explicitly uses the “use strict” directive to demonstrate that same receiver rule. In a non-strict classic script, a detached call can use the global receiver instead; do not rely on that fallback.

fn.call(receiver, argument) chooses a receiver for one call. fn.bind(receiver) returns a new function with the receiver fixed. Keep that returned function if you later need to remove it from a listener; binding twice creates two different function objects.

Arrow functions capture this from their surrounding context and cannot have it rebound with call or bind. For a callback that should access one particular object, an explicit closure such as () => counter.add() is often easiest to read. Do not rely on an external callback API to supply the receiver you need. The example uses call and bind explicitly so it behaves consistently across the browser editor and Node.

Predict, run, and explain

Write the output and trace the important state changes before checking it. Then run the program. Each editor is independent. Async examples end with main() so this editor waits for their work.

Predict, then run JavaScript
"use strict";

const counter = {
  value: 0,
  add(amount) {
    this.value += amount;
    return this.value;
  }
};
const detached = counter.add;

try {
  detached(1);
} catch (error) {
  console.log(error instanceof TypeError);
}

console.log(detached.call(counter, 2));

const add = detached.bind(counter);

console.log(add(3));
console.log(counter.value);
Check your prediction
true
2
5
5

The receiver comes from the call site

A normal function does not permanently remember the object it was read from. The call form supplies its receiver: object.method() uses object, while a detached call loses that connection. An arrow closure can deliberately capture the object and call its method later.

Call and bind solve different problems. Call invokes immediately with a chosen receiver; bind returns a new function to invoke later. Binding is not a copy of the receiver’s data, so later receiver changes remain visible through the bound function.

The receiver comes from the call site: trace JavaScript
"use strict";

const book = {
  title: "Old",
  label() {
    return this.title;
  }
};
const bound = book.label.bind(book);

book.title = "New";
console.log(bound());
console.log(book.label.call({ title: "Other" }));
Check your prediction
New
Other

The bound function retained the receiver, not a snapshot of Old. Call supplies a different receiver for just that invocation.

The receiver comes from the call site: contrast JavaScript
"use strict";

const counter = {
  value: 0,
  next() {
    this.value++;
    return this.value;
  }
};
const callback = () => counter.next();

console.log(callback());
console.log(counter.value);
console.log(counter.next === callback);
Check your prediction
1
1
false

The arrow wrapper is another function. Its body performs a method call with the right receiver. This is a useful callback boundary when passing the method itself would detach it.

Try the rule on new inputs

Trace object call, detached call, call, bind and arrow wrapper independently. Keep strict mode in receiver experiments because the console editor begins with a classic script rather than an ES module.

Your turn

Exercise

this, call() & bind(): practise

Create a meter with value:10 and an increase(amount) method. Bind a callback to that meter, call it with 5 and 2, then print 15 and 17. Do not use a global value variable.

Your workspace JavaScript
// Keep the returned bound function and use it twice.
Hint 1: the idea

The callback must retain the intended receiver after being detached.

Hint 2: a first step

Use meter.increase.bind(meter) once.

Show one solution
const meter = {
  value: 10,
  increase(amount) {
    this.value += amount;
    return this.value;
  }
};
const increase = meter.increase.bind(meter);

console.log(increase(5));
console.log(increase(2));
15
17

Explain why the implementation meets the stated contract. Which part is enforced by the language or checker, and which part needs behavioral evidence?

Change it and test it

Compare a bound function with a closure that calls meter.increase. Are two separate bind calls strictly equal? Verify they are not, then explain how that could break removeEventListener. Do not pass receiver-dependent methods directly to an API unless its receiver contract is explicit.

Was this article helpful?

Your response is saved on this device.