Handbooks / JavaScript / Chapter 3

this and Prototypes

7 pages · ~5 min✓ Reviewed

Builds on Scope and Closures. Next up: Event Loop and Async.

JavaScriptPage 1

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 = globalThis in sloppy mode
  • Strict mode gives undefined; modules and classes are always strict
  • obj.fn() sets this = 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

AskRulethis
new?newthe new object
call/apply/bind?explicitctx
obj.?implicitobj
plain?defaultglobalThis / undefined
arrowlexicalenclosing this
  1. newhighest
  2. explicit
  3. implicit
  4. defaultlowest
Read the call

Look at the call, not the definition: new? call/apply/bind? obj.? plain?

JavaScriptPage 2

Explicit, new and Arrow Binding

1. call, apply, bind

RunsArgs
fn.call(ctx, a, b)nowone by one
fn.apply(ctx, [a, b])nowone array
fn.bind(ctx, a)later, returns a NEW fnpartial args
  • this = ctx for that call
  • bind does not run the function
  • Bound this is permanent: a second bind or call cannot change it

2. new and arrows

  1. Empty objectlinked to Fn.prototype
  2. Run Fnthis = that object
  3. Return itunless Fn returns another object
  • Arrows have no own this, arguments or prototype
  • this is captured lexically from the enclosing scope
  • call/apply/bind cannot rebind an arrow; new throws

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.

JavaScriptPage 3

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, then null
  • Not found anywhere gives undefined
  1. objown props
  2. Fn.prototypeshared
  3. Object.prototypetoString...
  4. nullundefined

2. Links, Writes, Create

  • Fn.prototype becomes the [[Prototype]] of new Fn() objects
  • obj.__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 dictionary
  • Object.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

CheckLooks at
hasOwnProperty(k) / Object.hasOwn(o, k)own only
k in objown + inherited
a instanceof Fnis Fn.prototype in a's chain?
JavaScriptPage 4

Classes as Sugar over Prototypes

1. What class Really Is

  • class A { m() {} } puts m on A.prototype: shared, non-enumerable
  • constructor is the function body of A
  • typeof A === 'function'

2. extends and super

extends sets two links
  1. 1B.prototype[[Prototype]] = A.prototype
  2. 2B[[Prototype]] = A
  • Second link means static methods inherit too
  • Derived constructor: super(...) before this
  • Otherwise ReferenceError

3. Not Quite Plain Functions

functionclass
Call without newrunsTypeError
Strict modesloppy defaultalways
Hoistingusable earlyTDZ
  • 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.

JavaScriptPage 5

Common this Mistakes

1. Lost binding

  • const f = obj.method; f() loses implicit binding
  • Falls to default: undefined in strict mode, so this.x throws 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

MistakeWhyFix
{ hi: () => this.name }Arrow takes outer this, not the objectMethod hi() {}
addEventListener('click', fn.bind(o))bind returns a new function each timeKeep 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.

JavaScriptPage 6

this and Prototypes Cheat Sheet

1. Resolving this

  1. new Fn()this = new object
  2. call / apply / bindthis = ctx
  3. obj.fn()this = obj
  4. fn()undefined (strict)
  • Arrow: ignores all four, uses the enclosing this
  • Ask at each call: new? explicit? obj.? plain?
MethodRunsArgs
call(ctx, a, b)nowlisted
apply(ctx, [a, b])nowarray
bind(ctx, a)later, returns functionpartial

2. Prototypes and classes

  • Lookup: own property, then [[Prototype]] chain, then undefined
  • Object.getPrototypeOf(o) reads the link; Object.create(p) sets it
  • Object.hasOwn(o, k) own only; k in o adds inherited
  • a instanceof Fn: is Fn.prototype in a's chain
  • Class = constructor function + methods on .prototype
  • extends links both prototype and static chain
  • super() before this, 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.

JavaScriptPage 7

Check yourself

What does this print?
  • A TypeError.
  • g() is a plain call, so default binding applies.
  • Class bodies are strict, so this is undefined.
class C { n = 1; get() { return this.n; } }
const g = new C().get;
console.log(g());
What does this print?
  • 1 3
  • bind fixes this permanently; the second bind is ignored.
  • call on the original f is 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 false
  • a.list.push only reads list, found on the prototype.
  • The shared array is mutated, so b sees 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'));