Classes
Prerequisites: we assume you know functions, this binding, and the prototype chain — classes are syntax over that machinery, not a new object model.
Classes as prototype sugar
A class declaration creates a function whose prototype holds the methods. Instances share methods through the prototype chain; nothing is copied per instance. Fields declared in the class body, however, are per-instance properties created in the constructor.
class Greeter {
static kind = "greeter"; // class-level, one copy
#attempts = 0; // private field, per instance
constructor(name) { this.name = name; }
greet() { return `Hello, ${this.name}`; }
get attempts() { return this.#attempts; }
}
const g = new Greeter("Ada");
console.log(Object.getPrototypeOf(g) === Greeter.prototype); // true
console.log(typeof Greeter); // "function"Gotchas that bite experienced developers
- Class bodies are strict mode and non-hoisted. Unlike function declarations, classes are in the temporal dead zone until evaluated.
- Methods are non-enumerable on
prototype, but assignment (this.x = ...) is enumerable.Object.keyson instances misses prototype methods. newis mandatory. Calling a class withoutnewthrows aTypeError; a normal constructor function would not.- Private fields (
#x) are brand-checked, not just name-hiding: passing an object that lacks the field to a method expecting it throws even if it has a same-named public property.
Subclassing and extends
extends wires Child.prototype's prototype to Parent.prototype and makes super available. You must call super() before touching this in a derived constructor. static methods inherit too — through a second link (Object.getPrototypeOf(Child) === Parent).
class Timed extends Greeter {
constructor(name, at) {
super(name);
this.at = at; // ReferenceError if before super()
}
}Trade-off: deep inheritance is usually the wrong tool in JavaScript. Composition with factories or mixins avoids the fragile-base-class problem and the awkwardness of super across sibling hierarchies.
Map, Set, and Built-in Collections
Plain objects coerce keys to strings and collide with prototype properties. Map keeps insertion order, accepts any key type, and offers O(1) size, iteration, and deletion.
| Feature | Object | Map |
|---|---|---|
| Key types | strings, symbols | any value, incl. objects |
| Key equality | string identity | SameValueZero (`NaN` works) |
| Size | manual | `map.size` |
| Iteration order | mostly insertion (integer keys first) | guaranteed insertion |
Set stores unique values with the same equality rule — ideal for deduplication ([...new Set(xs)]) and membership tests.
WeakMap and WeakRef
WeakMap holds keys weakly: a key object unreachable elsewhere can be garbage-collected, so it's the right structure for attaching metadata to objects you don't own, without leaking memory.
const meta = new WeakMap();
const el = { tag: "div" };
meta.set(el, { mounted: true });
// when el becomes unreachable, its entry is collectableSelecting a structure
- Keyed lookup with heterogeneous keys →
Map. - Uniqueness / membership →
Set. - Tags or caches keyed by object identity →
WeakMap.
const events = new Map(); // node -> Set of handlers
function on(node, fn) {
if (!events.has(node)) events.set(node, new Set());
events.get(node).add(fn);
}
function off(node, fn) { events.get(node)?.delete(fn); }Exercise
Why does new Set([1, "1"]) have size 2, and how does that differ from an object literal's keys?
Show answer
Set uses SameValueZero equality on the actual values, so the number 1 and the string "1" are distinct entries. An object literal would coerce "1" and [1]'s key forms to the same string key "1", colliding.
Exercise
A teammate reads a private-style property by convention (obj._cache) and complains #cache "does the same thing." What's the real difference?
Show answer
#cache is enforced by the engine with a brand check: the field exists only on instances created by that class, so foreign objects with a same-named public property still throw. Underscore-prefixed properties are plain public fields with no enforcement.
Spotted a mistake, or code that does not work?