Lesson 10·M1 Core Mechanics
`this` and Binding
OpenLearning Objectives
By the end of this lesson you should be able to:
- State the four binding rules and their precedence
- Explain why a method loses
thiswhen you pass it as a callback - Say exactly how arrow functions differ, and why that's the point
- Fix a broken
thisthree different ways - Explain
thisin classes, callbacks and React
this Is Decided at Call Time
Same function, three call sites, three answers:
'use strict';
function whoAmI() {
return this;
}
const obj = { name: 'obj', whoAmI };
console.log(obj.whoAmI().name); // called as a method
console.log(whoAmI()); // called plain, in strict mode
console.log(whoAmI.call({ name: 'explicit' }).name);
Output:
obj
undefined
explicit
Nothing about the function body changed. Only the call did.
The Four Rules
Check them in this order — the first one that applies wins.
| # | Rule | Call looks like | this is |
|---|---|---|---|
| 1 | new binding | new Fn() | The brand-new object |
| 2 | Explicit binding | fn.call(o), fn.apply(o), fn.bind(o) | Whatever you passed |
| 3 | Implicit binding | obj.fn() | The object left of the dot |
| 4 | Default binding | fn() | undefined in strict mode, globalThis in sloppy |
function show() {
return this?.label ?? 'no this';
}
const owner = { label: 'owner', show };
console.log(new (function Ctor() { this.label = 'constructed'; })().label);
console.log(show.call({ label: 'explicit' }));
console.log(owner.show());
console.log(show());
Output:
constructed
explicit
owner
no this
Note
"The object left of the dot" is the shortcut for rule 3, and it's what makes rule 4 feel surprising — take the dot away and there's nothing left to bind to.
Losing this
This is the bug you'll actually meet. Extracting a method throws away the object it came from:
'use strict';
const counter = {
count: 0,
increment() {
this.count += 1;
return this.count;
},
};
console.log(counter.increment());
const detached = counter.increment;
try {
detached();
} catch (e) {
console.log(e.constructor.name);
}
Output:
1
TypeError
detached is the same function, called with no dot — so this is undefined and reading
this.count throws. The identical thing happens with callbacks:
setTimeout(counter.increment, 0); // ❌ same problem
element.addEventListener('click', counter.increment); // ❌
Three fixes:
setTimeout(() => counter.increment(), 0); // ✅ keep the call site intact
setTimeout(counter.increment.bind(counter), 0); // ✅ bind it permanently
const bound = counter.increment.bind(counter); // ✅ store the bound copycall, apply and bind
function introduce(greeting, punctuation) {
return `${greeting}, I'm ${this.name}${punctuation}`;
}
const person = { name: 'Ali' };
console.log(introduce.call(person, 'Hi', '!'));
console.log(introduce.apply(person, ['Hello', '.']));
const bound = introduce.bind(person, 'Hey');
console.log(bound('?'));
Output:
Hi, I'm Ali!
Hello, I'm Ali.
Hey, I'm Ali?
call takes arguments one by one, apply takes an array — apply for array is the
usual mnemonic. bind doesn't call anything: it returns a new function with this locked in,
and can pre-fill arguments (partial application, straight from Lesson 5).
A bound function can't be re-bound:
const rebound = bound.bind({ name: 'Someone else' });
console.log(rebound('!'));
Output:
Hey, I'm Ali!Arrow Functions Take this Lexically
Arrow functions have no this of their own. They use the this of the scope they were
written in — which is why they're immune to all four rules above.
const timer = {
label: 'timer',
startBroken() {
setTimeout(function () {
console.log('broken:', this?.label); // its own `this`
}, 0);
},
startFixed() {
setTimeout(() => {
console.log('fixed:', this.label); // the enclosing `this`
}, 0);
},
};
timer.startBroken();
timer.startFixed();
Output:
broken: undefined
fixed: timer
That's what arrow functions are for. "Shorter syntax" is the answer that costs you the
point — lexical this is the reason they exist.
The flip side: an arrow function is the wrong choice for a method, because there's no enclosing object scope to inherit from.
const bad = {
name: 'bad',
greet: () => (this === bad ? 'got the object' : 'this is NOT the object'),
};
const good = {
name: 'good',
greet() { return this === good ? 'got the object' : 'this is NOT the object'; },
};
console.log(bad.greet());
console.log(good.greet());
Output:
this is NOT the object
got the object
Whatever this is at that point in the file — undefined in a module, the global object in a
script — it is never the object literal, because an object literal doesn't create a scope.
this in Classes
Class bodies are always strict, so a detached method gets undefined rather than the global
object:
class Counter {
count = 0;
increment() {
this.count += 1;
return this.count;
}
// A class field holding an arrow — bound to the instance at construction.
incrementBound = () => {
this.count += 1;
return this.count;
};
}
const c = new Counter();
const loose = c.increment;
const safe = c.incrementBound;
console.log(c.increment());
try { loose(); } catch (e) { console.log(e.constructor.name); }
console.log(safe());
Output:
1
TypeError
2
The class-field arrow is the standard fix for passing a method as a callback — it's created
per instance with this already fixed, which is exactly the trade the memory argument from
Lesson 9 describes.
Where This Bites in React
class Old extends React.Component {
handleClick() {
this.setState({ clicked: true }); // ❌ `this` is undefined when passed directly
}
render() {
return <button onClick={this.handleClick}>Click</button>;
}
}
Fixes: bind in the constructor, use a class-field arrow, or wrap in an inline arrow. This is
the single biggest reason function components and hooks feel simpler — with no this in
sight, an entire category of bug disappears.
Pitfall
this in a plain function at module top level isn't the global object in a module — ES
modules are strict and their top-level this is undefined. In a CommonJS file it's
module.exports, and in a classic script it's globalThis. Same code, three answers.
Common Interview Questions
Q1. How is this determined?
By how the function is called, not where it's defined. In order of precedence:
newbinding, then explicit binding withcall/apply/bind, then implicit binding — the object left of the dot — then the default, which isundefinedin strict mode andglobalThisin sloppy mode.Arrow functions are the exception: they have no
thisand use the enclosing scope's.
Q2. What's the difference between call, apply and bind?
callandapplyinvoke the function immediately with a giventhis—calltakes arguments individually,applytakes them as an array.binddoesn't invoke; it returns a new function withthispermanently fixed, and can pre-fill arguments.A bound function can't be re-bound later.
Q3. Why does a method lose this when passed as a callback?
Because passing it copies the function reference, not the object. When the callback is later invoked there's no dot, so implicit binding never applies and you fall through to the default rule.
Fix it by preserving the call site with an arrow wrapper, or binding it.
Q4. Why do arrow functions exist?
For lexical
this. An arrow has no binding of its own, so inside a callback it still sees thethisof the surrounding code — which removes the oldconst self = thisdance.The shorter syntax is a bonus, not the reason.
Q5. When should you not use an arrow function?
As an object method or a prototype method, because there's no enclosing object scope to inherit
thisfrom. Also as a constructor — arrows can't be called withnew— and as a DOM event handler if you wantthisto be the element.
Q6. What is this inside a plain function call in strict mode?
undefined. In sloppy mode it's the global object, which is exactly the silent-failure case strict mode was designed to remove — you get aTypeErrorat the mistake instead of accidentally writing to a global.
Senior follow-up: What does new do to this, and how does it beat the other rules?
newcreates a fresh object, links it to the constructor's prototype, and calls the constructor withthisbound to that object — so it wins over implicit binding entirely.It even beats a bound function for the
thisvalue: callingnewon a function created bybindignores the boundthisand uses the new instance, though pre-filled arguments still apply.
Best Practices
✅ Use arrow functions for callbacks, so this keeps flowing from the surrounding scope
✅ Use regular functions or class methods for anything called as obj.method()
✅ Use a class-field arrow when a method will be passed as a callback
✅ Write 'use strict' or use modules, so a lost this throws instead of hitting the global
❌ Don't use an arrow as an object method
❌ Don't reach for const self = this — that's the pre-2015 workaround arrows replaced
Coding Exercise
Predict every line, then run it.
'use strict';
const obj = {
name: 'obj',
regular() { return this?.name; },
arrow: () => (this === obj ? 'obj' : 'not obj'),
nested() {
const inner = () => this?.name;
return inner();
},
};
console.log(obj.regular());
console.log(obj.arrow());
console.log(obj.nested());
const loose = obj.regular;
console.log(loose());
console.log(obj.regular.call({ name: 'other' }));
Answers
obj
not obj
obj
undefined
other
The method gets the object; the arrow property never does; an arrow inside a method
inherits that method's this; a detached method falls through to the default rule; explicit
binding overrides everything below it.
Mini Challenge
Implement myBind(fn, thisArg, ...preset) without using the built-in bind — returning a
new function that calls fn with the fixed this and the preset arguments prepended.
Then check your version against the real thing on a method that's been detached from its object.
Lesson Summary
Recap
thisis set by the call, not the definition- Precedence:
new→ explicit (call/apply/bind) → implicit (left of the dot) → default - Default is
undefinedin strict mode,globalThisin sloppy mode - Passing a method as a callback drops the dot, and with it the binding
- Arrow functions have no
this— they use the enclosing scope's, which is why they exist - Never use an arrow as an object or prototype method
- Class fields holding arrows are the standard fix for callback methods
Check your understanding
Answer each one out loud. Then rate yourself honestly.
0/6State the four binding rules in precedence order.
What is this in a plain function call, in strict mode and in sloppy mode?
Explain exactly why setTimeout(obj.method, 0) breaks, and give two fixes.
What's the difference between call, apply and bind?
Why is "shorter syntax" the wrong answer to "why do arrow functions exist"?
Name two places an arrow function is the wrong tool.
What's Next
That closes M1 · Core Mechanics. Next is Lesson 11 — Functions: Declarations vs
Expressions, where hoisting and this meet the last piece: how a function is created
decides how it behaves.