Cheat sheet · JavaScript Part 2: Objects, Async and Modules

JavaScript Part 2 cheat sheet

The deeper half of JavaScript: prototypes, this and classes, promises, modules, iterators, the DOM, errors and copying, and memory. Rules and traps for the night before an interview.

Read the full series →

blogs · jp-blogs.vercel.app/cheatsheets/series/javascript-2

Prototype chain and newLesson 0 →

  • An object is a bag of properties plus one hidden [[Prototype]] link. A read walks the chain until null; a write makes an own property.
  • Fn.prototype becomes the prototype of new Fn() objects. __proto__ reads or writes any object's link; prefer Object.getPrototypeOf.
  • new = create an object from Fn.prototype, run Fn with this = it, return it (unless Fn returns another object).
function myNew(Ctor, ...args) {
  const obj = Object.create(Ctor.prototype);
  const r = Ctor.apply(obj, args);
  return r instanceof Object ? r : obj;
}

Watch out: obj.count++ on an inherited count creates an own property. obj.list.push(1) on an inherited array changes the shared one. Put mutable defaults in the constructor.

Descriptors, freeze and own keysLesson 0 →

CallAddDeleteChange values
preventExtensionsNoYesYes
sealNoNoYes
freezeNoNoNo
  • All three are shallow. Sloppy mode fails silently; strict mode throws TypeError.
  • Descriptor: value, writable, enumerable, configurable (or get, set). Assignment sets all on; defineProperty sets all off.
  • in and for...in see the whole chain. Object.hasOwn, keys, entries see own properties only.

How this is decidedLesson 1 →

OrderCall sitethis is
1new Fn()The new object
2fn.call(x), apply, bindx
3obj.fn()obj (the receiver)
4fn()undefined (strict) or global
ArrowAnywherethis of the code around it; fixed
fn.call(x, a, b)
Run now, arguments listed
fn.apply(x, [a, b])
Run now, arguments in an array
fn.bind(x, a)
Return a new function, this fixed, not run

Watch out: const f = obj.method; f() loses this. Fix: wrapper () => obj.method(), bind, or an arrow field. removeEventListener needs the same reference.

Classes, private fields, superLesson 1 →

class Animal {
  #age = 0;                 // truly private
  static count = 0;
  constructor(name) { this.name = name; }
  speak() { return this.name; }
}
class Dog extends Animal {
  constructor(name) {
    super(name);       // before any this
    this.tricks = [];
  }
}
  • A class is a function; methods sit on Class.prototype. Extras over functions: needs new, always strict, methods not enumerable, #private.
  • #x is enforced and hidden from keys and JSON; _x is only a habit.
  • this before super() is a ReferenceError. Child fields are not set while the parent constructor runs.
  • instanceof checks Ctor.prototype on the chain. Use Array.isArray for arrays. Mixin: const M = Base => class extends Base {}.

Promises and the event loopLesson 2 →

  • States: pending, then fulfilled or rejected, once. Handlers always run later as microtasks.
  • then returns a new promise: a returned value fulfills, a returned promise is followed, a throw rejects, a missing handler passes through.
  • Order: sync code, then nextTick (Node CJS), then promise microtasks, then timers. Microtasks always beat the next timer.
setTimeout(() => log("timer"), 0);
Promise.resolve().then(() => log("p"));
process.nextTick(() => log("tick"));
log("sync");
// sync, tick, p (promise), timer

Node CommonJS. In a browser there is no nextTick.

Combinators and async/awaitLesson 2 →

MethodFulfills whenRejects when
allAll succeedFirst failure
allSettledAll finish (reports each)Never
raceFirst to settle winsIf first settled is a failure
anyFirst successAll fail: AggregateError
const [a, b] = await Promise.all(
  [getA(), getB()]);
const ctl = new AbortController();
fetch(url, { signal: ctl.signal });
ctl.abort();                    // cancel
AbortSignal.timeout(3000);   // TimeoutError

Two awaits in a row cost the sum; start both first for the max.

Watch out: forEach does not wait for await. return await is needed inside try/catch. An unhandled rejection ends a Node 15+ process with exit code 1.

CommonJS vs ES modulesLesson 3 →

TopicCommonJSES modules
Syntaxrequire, module.exportsimport, export
LoadedRun time, synchronousStatic: link, then run
ValuesCopy of module.exportsLive bindings
Extrasrequire inside ifTop-level await, tree shaking
  • exports only points at module.exports. exports = {} does nothing; module.exports = {} replaces all.
  • Node: .mjs is ESM, .cjs is CJS, .js follows package.json "type". ESM needs full file extensions.
  • CJS loads ESM with await import(); require() works on Node 22.12+ unless the module uses top-level await.
const m = await import("./chart.js");

import() returns a promise of the namespace; bundlers split one file per call. Prefer named exports over default.

Watch out: Cycles: CJS gives a partly filled export; ESM const exports hit the temporal dead zone. Fix by moving shared code to a third module. Tree shaking is blocked by CJS and side effects.

Iterators and generatorsLesson 4 →

  • Iterable: has a Symbol.iterator method. Iterator: has next() returning { value, done }. A generator is both.
  • for...of calls Symbol.iterator, loops next(), and calls return() on break, return or throw.
  • for...in gives string keys, inherited too. for...of gives values and throws on plain objects.
  • next(x) becomes the paused yield's result; the first next argument is dropped. throw() and return() finish it.
function* naturals() {
  let n = 1;
  while (true) yield n++;  // infinite
}
const odd = naturals()
  .filter(n => n % 2).take(3);
odd.toArray();      // [1, 3, 5] Node 22

async function* pages() {  // for await
  for (let i = 1; ; i++)
    yield await load(i);
}

SymbolsLesson 4 →

Symbol.iterator
Powers for...of and spread
Symbol.asyncIterator
Powers for await
Symbol.toPrimitive
Hint: default, string or number
Symbol.toStringTag
The X in [object X]
Symbol.hasInstance
Customises instanceof
Symbol.species
Constructor used by map/filter in subclasses

Tip: Symbols are unique, hidden from keys and JSON, but not private: getOwnPropertySymbols finds them. Use #private for privacy.

DOM and eventsLesson 5 →

PairDifference
childNodes / childrenAll nodes with text / elements only
querySelectorAllStatic NodeList snapshot
getElementsByTagNameLive HTMLCollection
stopPropagationStops travel to other elements
preventDefaultStops browser action (needs cancelable)
target / currentTargetOrigin (fixed) / current listener (changes)
  • Phases: capture (down), target, bubble (up). Default listeners run on bubble; { capture: true } runs on the way down.
  • Use textContent for untrusted text. innerHTML can run code (XSS) and += destroys listeners.
  • focus, blur, mouseenter do not bubble; use focusin, focusout.

Delegation, options and custom eventsLesson 5 →

list.addEventListener("click", e => {
  const li = e.target.closest("li");
  if (li) select(li);
}, { signal: ctl.signal });

el.dispatchEvent(new CustomEvent(
  "app:save",
  { detail: { id: 1 }, bubbles: true }));

ctl.abort() removes the listener. dispatchEvent is synchronous. Custom events do not bubble by default.

  • Options: once, capture, passive (promise not to preventDefault, keeps scrolling smooth), signal.
  • removeEventListener needs the same function reference; a new arrow never matches.
  • Setting .value fires no input/change. input fires per edit; change on commit. jsdom has no layout (all sizes 0).

Errors and async errorsLesson 6 →

class AppError extends Error {
  constructor(msg, opts) {
    super(msg, opts);   // passes cause
    this.name = "AppError";
  }
}
try { await load(); }  // await inside try
catch (e) {
  throw new AppError("load", { cause: e });
}
finally { release(); }
  • Throw Error objects: they have stack, cause and instanceof. A thrown string has no stack.
  • return inside finally replaces the earlier return and any error.
  • JSON.stringify(err) is {}: message and stack are not enumerable. Build { name, message, stack, cause }.
  • try { asyncFn(); } misses the rejection. Use await inside try, or .catch.

Proxy and ReflectLesson 6 →

const user = new Proxy({}, {
  set(t, k, v, r) {
    if (k === "age" && !Number.isInteger(v))
      throw new TypeError("age");
    return Reflect.set(t, k, v, r);
  },
});

Reflect does the default and returns true/false; it takes a receiver.

  • Traps cover get, set, delete, in, keys and calls. Use cases: validation, logging reads.
  • Limits: #private fields fail through a proxy, invariants block lies about read-only props, and reads were about 6x slower in the lesson run.

Copying and immutable updatesLesson 6 →

ToolResult
Spread, assign, slice, concatShallow: nested objects are shared
JSON.parse(JSON.stringify(x))Dates become strings; undefined, functions, Map lost
structuredCloneDeep; keeps Date, Map, Set, cycles
structuredClone limitsThrows on functions, symbols, WeakMap; loses prototype
const next = { ...state,
  user: { ...state.user,
    prefs: { ...state.user.prefs,
      theme: "light" } } };
const s = arr.toSorted((a, b) => a - b);
// also toReversed, toSpliced, with(i, v)

toSorted with no compare function still sorts as text.

Watch out: Object.freeze is shallow: nested edits and a frozen Map's set still work. Use a recursive deepFreeze, or readonly types.

Garbage collection and leaksLesson 7 →

  • Freed by reachability from roots (globals, stack, timers, listeners), not by whether you still use it.
  • Five usual leaks: unbounded cache, forgotten listener, uncleared timer, closure capturing too much, detached DOM node.
  • WeakMap/WeakSet: keys are objects, held weakly, not iterable, no size. WeakRef and FinalizationRegistry are never for correctness.
  • Buffers live outside the heap: watch arrayBuffers and rss, not only heapUsed.
  1. Confirm growth in rss and heapUsed; reproduce in a loop with --expose-gc.
  2. Take two heap snapshots (--inspect or v8.writeHeapSnapshot()).
  3. Compare, sort by retained size, follow the retainer chain to a root.
  4. Cut that path, then check memory stays flat.

Interview one-linersLesson 0 →

QuestionShort answer
__proto__ vs prototype?prototype is on constructors; __proto__ is any object's actual link.
call vs apply vs bind?Run with listed args; run with array; return a bound function.
Class just sugar?Mostly; plus needs new, strict, #private.
all vs allSettled?all fails on the first failure; allSettled reports every outcome.
CJS vs ESM?Run-time copies vs static live bindings.
Shallow vs deep copy?Shallow shares nested objects; structuredClone copies all levels.
Map vs WeakMap?Map keeps keys alive; WeakMap lets them be collected.
Memory leak?Memory you no longer need but can still reach.

A cheat sheet is a reminder, not a teacher. Each card links to the lesson that explains it step by step.