A method works perfectly, then you pass it as an argument and it breaks. Not a single line of the code changed. What changed is the way it is called.
That is the peculiarity of this in JavaScript: its value is decided not where the function is written, but where it is called. That resolution is known as this binding.
Definition
this binding is the rule that determines, at call time, which object the function will refer to as this. Four call forms exist, plus a fifth that escapes the rule entirely.
| Call form | What this holds |
|---|---|
new Account() | The freshly created object |
account.introduce() | The object written before the dot |
introduce.call(account) | The first argument supplied |
introduce() | undefined in a module, the global object otherwise |
() => ... | Whatever it was where the arrow was written |
This two-column table reads top to bottom: the first form matching the call wins.
The case that breaks, and why
const account = {
name: "Believemy",
introduce() {
return this;
},
};
console.log(account.introduce() === account); // true
const detached = account.introduce;
console.log(detached() === account); // false
const bound = account.introduce.bind(account);
console.log(bound() === account); // trueThe method does not belong to the object, it is merely stored there. Taken out of its slot, it becomes an ordinary function called without a dot, and therefore without an object to point at. In a module, this holds undefined and any property read throws a TypeError.
The three ways to reset the binding
bind()returns a new function whosethisis frozen. That is the answer when the function travels elsewhere.call()andapply()call immediately while forcingthis. They differ only in how arguments are passed.- The Arrow function has no
thisof its own: it reuses the one from where it was written, which solves the problem without thinking about it.
const timer = {
name: "stopwatch",
start() {
setTimeout(() => {
console.log("done: " + this.name);
}, 10);
},
};
timer.start(); // done: stopwatchWith a classic function in place of the arrow, this.name would no longer point at the timer: the callback is called by the engine, with no object in front of it.
Frequently asked questions
Why does my class method lose its this when passed as an argument?
Because passing object.method hands over the function alone, detached from the object. The fix is to pass object.method.bind(object), or to wrap the call in an arrow. Inside a class, declaring the method as an arrow field freezes the binding once and for all.
What is the difference between call() and apply()?
None in the result. call() takes arguments one by one, apply() takes them in an array. Since Spread (...) arrived, apply() has lost almost all of its purpose, because fn.call(target, ...list) does the same thing more readably.
Can an arrow function be rebound to another object?
No, and that is intentional. An arrow has no binding of its own, so bind(), call() and apply() have no effect on its this. That is exactly why it is reliable as a callback, and unsuitable as an object method or a constructor.