Handbooks / JavaScript / Chapter 3
this and Prototypes
7 pages · ~5 min✓ Reviewed
Builds on Scope and Closures. Next up: Event Loop and Async.
The this Mental Model
1. Definition
- this is decided by HOW a function is called: the call-site
- Not by where the function is defined
- Arrow functions are the exception: lexical
this
2. Default and Implicit
- Plain
fn()is default:this = globalThisin sloppy mode - Strict mode gives
undefined; modules and classes are always strict obj.fn()setsthis = obj- Only the object immediately left of the dot counts
javascript
const a = { b: { n: 'b', fn() { return this.n } } } a.b.fn() // 'b': this is b, not a
3. Comparison
| Ask | Rule | this |
|---|---|---|
new? | new | the new object |
| call/apply/bind? | explicit | ctx |
obj.? | implicit | obj |
| plain? | default | globalThis / undefined |
| arrow | lexical | enclosing this |
- newhighest
- explicit
- implicit
- defaultlowest
Read the call
Look at the call, not the definition: new? call/apply/bind? obj.? plain?
Explicit, new and Arrow Binding
1. call, apply, bind
| Runs | Args | |
|---|---|---|
fn.call(ctx, a, b) | now | one by one |
fn.apply(ctx, [a, b]) | now | one array |
fn.bind(ctx, a) | later, returns a NEW fn | partial args |
this = ctxfor that callbinddoes not run the function- Bound
thisis permanent: a second bind or call cannot change it
2. new and arrows
- Empty objectlinked to
Fn.prototype - Run Fn
this= that object - Return itunless Fn returns another object
- Arrows have no own this,
argumentsorprototype thisis captured lexically from the enclosing scope- call/apply/bind cannot rebind an arrow;
newthrows
3. Example: lost this
javascript
setTimeout(obj.method, 0); // this lost setTimeout(obj.method.bind(obj), 0); // fix 1 setTimeout(() => obj.method(), 0); // fix 2
Why it breaks
obj.method passed bare is a plain function: no obj. at call time, so default binding.
Prototype Chain Internals
1. The Lookup Chain
- Every object has an internal [[Prototype]], read with
Object.getPrototypeOf(o) - Lookup walks o, then its prototype, up to
Object.prototype, thennull - Not found anywhere gives undefined
- objown props
- Fn.prototypeshared
- Object.prototypetoString...
- nullundefined
2. Links, Writes, Create
Fn.prototypebecomes the [[Prototype]] ofnew Fn()objectsobj.__proto__is the legacy accessor to the same link- Writes go on the object and shadow the prototype; reads fall through
Object.create(proto)sets the prototype;Object.create(null)gives a chainless dictionaryObject.getPrototypeOf(new Fn()) === Fn.prototype
javascript
function Fn() {} Fn.prototype.x = 1; const a = new Fn(); a.x = 2; console.log(Object.getPrototypeOf(a) === Fn.prototype, a.x, Fn.prototype.x); console.log(Object.hasOwn(a, 'toString'), 'toString' in a, a instanceof Fn);
write shadows, prototype keeps 1
output
true 2 1 false true true
3. Checking Properties
| Check | Looks at |
|---|---|
hasOwnProperty(k) / Object.hasOwn(o, k) | own only |
k in obj | own + inherited |
a instanceof Fn | is Fn.prototype in a's chain? |
Classes as Sugar over Prototypes
1. What class Really Is
class A { m() {} }putsmon A.prototype: shared, non-enumerableconstructoris the function body ofAtypeof A === 'function'
2. extends and super
extends sets two links
- 1B.prototype[[Prototype]] = A.prototype
- 2B[[Prototype]] = A
- Second link means static methods inherit too
- Derived constructor:
super(...)beforethis - Otherwise ReferenceError
3. Not Quite Plain Functions
| function | class | |
|---|---|---|
Call without new | runs | TypeError |
| Strict mode | sloppy default | always |
| Hoisting | usable early | TDZ |
- Field
handle = () => {...}is an own property, per instance - Not on the prototype; the usual fix for lost
this
javascript
class Btn { handle = () => this.label m() {} } Object.hasOwn(new Btn(), 'handle') // true Object.hasOwn(new Btn(), 'm') // false
Remember
A class is a constructor function plus methods on .prototype. Arrow fields cost one function per instance.
Common this Mistakes
1. Lost binding
const f = obj.method; f()loses implicit binding- Falls to default:
undefinedin strict mode, sothis.xthrows TypeError arr.forEach(function(){ this })also gets default binding- Fix: an arrow function, or pass
thisArg
js
const f = obj.method; f(); // TypeError arr.forEach(function (x) { this.add(x); }, this); // thisArg
thisArg is the second argument of forEach
2. Arrow and bind traps
| Mistake | Why | Fix |
|---|---|---|
{ hi: () => this.name } | Arrow takes outer this, not the object | Method hi() {} |
addEventListener('click', fn.bind(o)) | bind returns a new function each time | Keep a reference |
3. new and prototypes
- No
new: sloppy mode sets properties on the global object Fn.prototype.list = []is shared by all instances- Put per-instance state in the constructor
Ask at the call
new? call/apply/bind? obj.? plain? Arrows ignore all four.
this and Prototypes Cheat Sheet
1. Resolving this
- new Fn()this = new object
- call / apply / bindthis = ctx
- obj.fn()this = obj
- fn()undefined (strict)
- Arrow: ignores all four, uses the enclosing this
- Ask at each call: new? explicit? obj.? plain?
| Method | Runs | Args |
|---|---|---|
call(ctx, a, b) | now | listed |
apply(ctx, [a, b]) | now | array |
bind(ctx, a) | later, returns function | partial |
2. Prototypes and classes
- Lookup: own property, then
[[Prototype]]chain, thenundefined Object.getPrototypeOf(o)reads the link;Object.create(p)sets itObject.hasOwn(o, k)own only;k in oadds inheriteda instanceof Fn: isFn.prototypein a's chain
- Class = constructor function + methods on
.prototype extendslinks both prototype and static chainsuper()beforethis, else ReferenceError
3. Callback lost this
- Arrow function
.bind(obj)- Class field arrow:
handle = () => {} const self = this
Extracted method
const f = obj.method; f() falls to default binding.
Check yourself
What does this print?
- A TypeError.
g()is a plain call, so default binding applies.- Class bodies are strict, so
thisisundefined.
class C { n = 1; get() { return this.n; } } const g = new C().get; console.log(g());
What does this print?
1 3bindfixesthispermanently; the secondbindis ignored.callon the originalfis unaffected.
function f() { return this.x; } const b = f.bind({ x: 1 }).bind({ x: 2 }); console.log(b(), f.call({ x: 3 }));
Why does hi() not return 'x', and what is the fix?
- An arrow function has no own
this; it takes the outer scope's. - Use method shorthand:
hi() { return this.name; }.
const o = { name: 'x', hi: () => this.name };
What does this print?
1 falsea.list.pushonly readslist, found on the prototype.- The shared array is mutated, so
bsees it.
function A() {} A.prototype.list = []; const a = new A(), b = new A(); a.list.push(1); console.log(b.list.length, a.hasOwnProperty('list'));