this, call, apply & bind
Closures decide which variables a function can see. this decides which object a function is working on. It is the most confusing part of JavaScript, and it caused the famous "lost this" bug in React class components.
Definition
this is a special value that is set when a function is called, not when it is written. It usually means "the object before the dot".
Analogy: the word "I". It means a different person depending on who is speaking. In the same way, this means a different object depending on how the function is called.
The Four Rules
JavaScript decides this using these rules, from strongest to weakest.
| Rule | How it is called | this is |
|---|---|---|
new | new Person() | The new object being created |
| Explicit | fn.call(obj), apply, bind | The obj you passed |
| Implicit | obj.fn() | obj (the object before the dot) |
| Default | fn() | undefined in strict mode, window otherwise |
1const user = {2 name: "Asha",3 hi() { console.log(this.name); },4};5 6user.hi(); // "Asha" (implicit: object before the dot)7 8const loose = user.hi;9loose(); // undefined ✗ (default rule, the dot is gone)Here is what breaks. Pulling a method out of its object loses this. This is exactly what happens when you pass a method as a callback.
Arrow Functions
Arrow functions have no this of their own. They use the this from the place where they were written (lexical, just like closures).
1const timer = {2 seconds: 0,3 start() {4 setInterval(() => {5 this.seconds++; // ✓ this is timer, taken from start()6 }, 1000);7 },8};With a normal function () {} inside setInterval, this would be lost. Because of this, arrows are the usual choice for callbacks. Do not use arrows for object methods or constructors, and call, apply and bind cannot change an arrow's this.
call, apply and bind
All three let you choose this yourself.
1function intro(greeting, punct) {2 return greeting + ", " + this.name + punct;3}4const asha = { name: "Asha" };5 6intro.call(asha, "Hi", "!"); // "Hi, Asha!" arguments one by one7intro.apply(asha, ["Hi", "!"]); // "Hi, Asha!" arguments as an array8const bound = intro.bind(asha, "Hi"); // returns a NEW function, does not run it9bound("?"); // "Hi, Asha?"| Runs now? | Arguments | Returns | |
|---|---|---|---|
call | Yes | Listed | Function result |
apply | Yes | Array | Function result |
bind | No | Listed (can be partial) | A new function |
A mnemonic: Apply takes an Array. Call takes commas.
In React
Class components needed bind because event handlers lose this:
1class Button extends React.Component {2 constructor(props) {3 super(props);4 this.handleClick = this.handleClick.bind(this); // ✓ fix lost this5 }6 handleClick() { this.setState({ clicked: true }); }7 render() { return <button onClick={this.handleClick}>Go</button>; }8}Without the bind, React calls handleClick as a plain function, this is undefined, and this.setState crashes. Another fix was an arrow property: handleClick = () => { ... }.
Function components do not have this problem. They have no this at all. State and props are plain variables captured by closures. That is one of the biggest reasons the ecosystem moved to function components and hooks.
Summary
1this → decided at call time, not at write time2Rules → new, explicit (call/apply/bind), implicit (obj.fn()), default3Lost this → happens when a method is detached from its object4Arrow functions → no own this, use the surrounding one5call → run now, args listed apply → run now, args in array bind → returns new function6React classes → bind in constructor or use arrow properties7Function components → no this, closures insteadNext we move from how functions behave to how JavaScript schedules them. That is the event loop.