PageSourceSearch

https://hoist-learn.netlify.app/chunk-DFrWDFf3.js

js hoist-learn.netlify.app collected 2026-10-03 10:54:58 UTC 7,456 bytes, 53 lines download raw bytes

1import{t as e}from"./chunk-CvV0FUaE.js";var o={id:`classes`,moduleId:`objects-protos`,trackId:`core-js`,title:`Classes, #private & static blocks`,blurb:`Sugar with sharp edges: field order, private brand checks, static init.`,minutes:12,level:`working`,tldr:{model:"`class` is mostly sugar over prototypes, but it adds things functions cannot express: a TDZ, strict mode, new-only calls, non-enumerable methods and real private state.",gotcha:"Construction order is `super()`, then subclass fields, then the body — so a parent constructor calling an overridden method reads the child's fields as `undefined`."},blocks:[{kind:`prose`,md:'`class` is mostly syntax over the prototype machinery you already know: methods land on `Prototype.prototype`, `extends` links the two prototype chains, `super` is a reference to the parent\'s.\n\nBut "mostly" is doing work. Classes added things that are **not** expressible with functions — a temporal dead zone, non-enumerable methods, a hard requirement to call `new`, genuinely private state, and a fixed initialisation order that catches people out in constructors.'},{kind:`code`,lang:`js`,caption:`What class actually produces`,code:`class Point {
2  constructor(x) { this.x = x; }
3  show() { return this.x; }
4}
5
6// Methods are on the prototype, and non-enumerable
7console.log(Object.hasOwn(Point.prototype, 'show'));                       // true
8console.log(Object.keys(Point.prototype));                                 // []
9console.log(typeof Point);                                                 // 'function'
10
11// Unlike a function, it cannot be called without new
12try { Point(1); } catch (e) { console.log(e.constructor.name); }           // TypeError`},{kind:`callout`,tone:`tip`,title:`Class bodies are always strict mode`,md:"Everything inside a class body runs in strict mode regardless of the surrounding file, and class declarations are **hoisted but in the TDZ** — referencing one before its definition throws `ReferenceError`, not `undefined`."},{kind:`heading`,text:`Fields vs prototype methods`},{kind:`prose`,md:"A class field is assigned to **the instance**, inside the constructor, once per object. A method is defined **once on the prototype** and shared.\n\nThat makes `m = () => {}` and `m() {}` genuinely different declarations, not styles. The arrow field captures `this` per instance and survives being detached — at the cost of one closure per object, and of being invisible on the prototype (so it cannot be spied on or overridden the usual way)."},{kind:`code`,lang:`js`,caption:`Where each one lives`,code:`class E {
13  m = () => 'field';
14  p() { return 'proto'; }
15}
16const e = new E();
17
18console.log(Object.hasOwn(e, 'm'));            // true  \u2014 per instance
19console.log(Object.hasOwn(e, 'p'));            // false
20console.log(Object.hasOwn(E.prototype, 'p'));  // true  \u2014 shared`,output:`true
21false
22true`},{kind:`compare`,aLabel:`Arrow field`,bLabel:`Prototype method`,rows:[{label:`Lives on`,a:`Each instance`,b:`The prototype`},{label:`this when detached`,a:`Still the instance`,b:`undefined`},{label:`Memory`,a:`One closure per instance`,b:`One function total`},{label:`Overridable by a subclass`,a:`Shadowed, not overridden`,b:`Yes`},{label:`super`,a:`Not available`,b:`Available`}]},{kind:`heading`,text:`Initialisation order`},{kind:`prose`,md:`The order inside a constructor is fixed and it explains a classic bug:
23
241. \`super(...)\` runs the parent constructor **to completion**.
252. Then the subclass's own fields are initialised, in source order.
263. Then the rest of the subclass constructor body runs.
27
28So if the parent constructor calls a method the child overrides, that method runs **before the child's fields exist**.`},{kind:`code`,lang:`js`,caption:`The parent calls an overridden method too early`,code:`class A {
29  constructor() { this.init(); }
30  init() { console.log('A.init'); }
31}
32class B extends A {
33  x = 1;
34  init() { console.log('B.init, x =', this.x); }
35}
36
37new B();`,output:`B.init, x = undefined`},{kind:`callout`,tone:`gotcha`,title:`Do not call overridable methods from a constructor`,md:"`this.init()` dispatches to the subclass, which then reads fields that `super()` has not let it initialise yet. Either do the work inline in the constructor, or expose an explicit `start()` the caller invokes after construction."},{kind:`heading`,text:`Private fields`},{kind:`prose`,md:"`#name` is real privacy, not a convention. It is not a property: it does not appear in `Object.keys`, `JSON.stringify`, `Reflect.ownKeys` or a `Proxy` trap, and it cannot be reached with `obj['#name']`.\n\nAccess is **lexically scoped to the class body** and checked by a *brand*: the object must have been constructed by that exact class. Touching `#name` on anything else throws a `TypeError` rather than returning `undefined`, which makes private fields a reliable instance test."},{kind:`code`,lang:`js`,caption:`The brand check, and the ergonomic form of it`,code:`class C {
38  #v = 1;
39  get v() { return this.#v; }
40  static has(o) { return #v in o; }   // ergonomic brand check, never throws
41}
42
43console.log(C.has(new C()), C.has({}));
44
45const getter = Object.getOwnPropertyDescriptor(C.prototype, 'v').get;
46try { getter.call({}); } catch (e) { console.log(e.constructor.name + ':', e.message); }`,output:`true false
47TypeError: Cannot read private member #v from an object whose class did not declare it`},{kind:`callout`,tone:`new`,title:`Use #field in obj instead of try/catch`,md:'The `in` form is the supported way to ask "is this one of mine?" — it is cheaper and clearer than wrapping an access in `try/catch`, and unlike `instanceof` it cannot be fooled by `Object.create(C.prototype)` or a changed `Symbol.hasInstance`.'},{kind:`heading`,text:`Static members and static blocks`},{kind:`prose`,md:"`static` puts a member on the **constructor** rather than the prototype, and statics are inherited: `class D extends C {}` gives `D` access to `C`'s statics, because `Object.getPrototypeOf(D) === C`.\n\nA `static { ... }` block runs once, at class-definition time, with `this` bound to the class and full access to its private names. It exists because some setup needs statements — a loop, a try, or wiring two classes together — which a single field initialiser expression cannot express."},{kind:`code`,lang:`js`,caption:`Static initialisation that needs statements`,code:`class D {
48  static reg = new Map();
49  static {
50    D.reg.set('k', 'v');
51  }
52}
53console.log(D.reg);`,output:`Map(1) { 'k' => 'v' }`},{kind:`callout`,tone:`interview`,title:`How this gets asked`,md:'Favourites: *"is class just syntactic sugar?"* (mostly, but list what is not: TDZ, strict mode, new-only, non-enumerable methods, real private state), *"arrow field vs method"*, and a constructor-order trace like the `B.init` one above.'},{kind:`takeaways`,items:[`Methods go on the prototype and are non-enumerable; fields are per-instance.`,`Classes are TDZ-hoisted, always strict, and throw if called without new.`,`Order is super(), then subclass fields, then the constructor body — so a parent calling an overridden method sees undefined fields.`,`An arrow field binds this per instance but costs a closure each and cannot use super.`,`#private is brand-checked: reading it off a foreign object throws, and #x in obj is the safe test.`,`Statics live on the constructor and are inherited; static blocks run once, at definition time.`]}],cards:e};export{o as CLASSES};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.