Part 20 — Advanced JavaScript
Execution context & the call stack, Memory, garbage collection, weak references, Symbols & well-known symbols, Iterators & generators, Tail calls
Execution context & the call stack#
Each function call pushes an execution context (with its variable environment, scope chain, and this) onto the call stack. Returning pops it. A stack overflow is too many nested contexts (usually runaway recursion).
graph TD
subgraph "Call Stack (top runs)"
c["multiply() context"]
b["square() context"]
a["main() / global context"]
end
c --> b --> a
Memory, garbage collection, weak references#
JS uses a mark-and-sweep GC: starting from roots (global, stack), it marks everything reachable; the unmarked is swept. You can't force GC. Leaks happen when you unintentionally keep references: forgotten timers, detached DOM nodes held in a variable, growing caches, closures over big data, and listeners never removed.
WeakMap/WeakSet/WeakRef/FinalizationRegistry hold objects weakly so they don't block collection — use for caches and metadata keyed by object.
Symbols & well-known symbols#
const id = Symbol("id"); // unique, non-colliding key
obj[id] = 1; // hidden from normal enumeration
Symbol.for("x") === Symbol.for("x"); // true — global registry
// well-known symbols customize built-in behaviour:
class Range {
constructor(a,b){this.a=a;this.b=b;}
*[Symbol.iterator]() { for (let i=this.a;i<=this.b;i++) yield i; }
}
[...new Range(1,3)]; // [1,2,3]
Iterators & generators#
// iterator protocol: an object with next() → { value, done }
function makeIter(arr) {
let i = 0;
return { next: () => i < arr.length ? {value:arr[i++],done:false} : {value:undefined,done:true},
[Symbol.iterator]() { return this; } };
}
// generators produce iterators effortlessly and can pause/resume
function* fib() { let [a,b]=[0,1]; while(true){ yield a; [a,b]=[b,a+b]; } }
const g = fib(); g.next().value; // 0 → 1 → 1 → 2 ...
// generators can receive values and delegate
function* outer(){ yield* [1,2]; const x = yield 3; return x; }
Async generators & iterators:
async function* pages(url) {
let next = url;
while (next) {
const res = await fetch(next); const data = await res.json();
yield data.items;
next = data.nextUrl;
}
}
for await (const items of pages("/api")) process(items);
Tail calls#
The spec defines proper tail calls (a call in tail position reuses the stack frame), but V8 does not implement it. So don't rely on TCO in Node/Chrome; convert deep recursion to loops.
Decorators (Stage 3 proposal) 🟡#
function logged(value, ctx) {
return function (...args) { console.log(`call ${ctx.name}`); return value.call(this, ...args); };
}
class Api { @logged fetchData() {} }
Available today via TypeScript/Babel; native support is arriving. Used for cross-cutting concerns (logging, memo, validation, DI).
Engine performance internals 🎯#
- Hidden classes (shapes/maps): V8 assigns objects an internal "shape" describing their properties. Objects created with the same properties in the same order share a shape → fast property access via offsets.
- Inline caches (ICs): V8 caches where a property lives for a given shape at each access site. Consistent shapes keep the IC "monomorphic" (fastest); mixed shapes make it "polymorphic" or "megamorphic" (slow).
- Deoptimization: if your assumptions break (a function suddenly gets a different type, an object changes shape mid-loop, you add properties dynamically), V8 throws away optimized code and falls back to bytecode.
Practical rules for fast code:
- Initialize all object properties in the constructor, in a consistent order — don't add properties later.
- Don't mix types in an array or a function's parameters if it's hot.
- Avoid
deleteon hot objects (deoptimizes shape); set tonullor use aMap. - Keep functions monomorphic (same argument shapes/types).
- Avoid
argumentsleaking; use rest params.
Interview questions (Part 20):
- Explain mark-and-sweep GC and name three common leak sources.
- What are hidden classes and inline caches, and how do they affect performance?
- Difference between an iterator and a generator; write a generator for the Fibonacci sequence.
- Does JS optimize tail calls? What are the implications?
- What are Symbols for? Give a well-known symbol example.