Irbisa · Cheatsheet
Advanced JavaScript
Type coercion, call, apply and bind, the new operator, Map, Set and WeakMap, iterators and generators, Proxy, garbage collection, output puzzles, writing Promise.all, curry and an event emitter
What is the difference between
Mapand a plain object, and betweenSetand an array?const user = { id: 7 }; const plain = {};plain[user] = 'Ada'; // the key becomes the string "[object Object]"plain[2] = 'two';plain.b = 'b';…What are
WeakMap,WeakSetandWeakRef, and when would you use them instead ofMapandSet?// derived data per state object: entries go away together with old statesconst totals = new WeakMap(); function getTotal(cart) { if (!totals.has(cart)) { console.log('computing');…What does each of these expressions evaluate to, and which coercion rules explain the results?
console.log([] + {});console.log('5' - 2);console.log('5' + 2);console.log(true + 1);console.log(null + 1);console.log(undefined + 1);console.log([] == ![]);console.log('b' + 'a' + +'a' + 'a');What do
call,applyandbinddo, and how would you write a polyfill forFunction.prototype.bind?Function.prototype.myBind = function (thisArg, ...preset) { const fn = this; return function bound(...args) { if (new.target) return new fn(...preset, ...args); // new ignores the bound this return fn.apply(thisArg, [...preset, ...args]); };…What does this code log, and why does
thischange between the calls?class Counter { count = 0; inc() { return ++this.count; } incArrow = () => ++this.count;} const c = new Counter();const other = { count: 10, inc: c.inc, incArrow: c.incArrow };console.log(c.inc());console.log(other.inc());console.log(other.incArrow());console.log(c.inc.bind(other).call(c));const { inc } = c;inc();What does the
newoperator do step by step, and how would you reimplement it as a plain function?function myNew(Constructor, ...args) { const obj = Object.create(Constructor.prototype); // 1. link the prototype const result = Constructor.apply(obj, args); // 2. run the body with this = obj const isObject = result !== null && (typeof result === 'object' || typeof result === 'function'); return isObject ? result : obj; // 3. an object returned by the body wins}…What do
Object.freeze,Object.sealand property descriptors do, and why is a frozen object not deeply immutable?const config = Object.freeze({ api: '/v1', retry: { count: 3 } });config.retry.count = 5; // allowed: freeze is shallowtry { config.api = '/v2'; // modules and classes are strict, so this throws} catch (error) { console.log(error.message); // Cannot assign to read only property 'api' of object '#<Object>'…What makes an object iterable, how does
Symbol.iteratorwork, and how doesfor...ofdiffer fromfor...in?class Range { constructor(from, to) { this.from = from; this.to = to; }…What are generator functions, and where are they still useful in frontend code?
function* idGenerator(prefix) { let n = 1; while (true) yield `${prefix}-${n++}`; // infinite, but lazy: runs only on next()} const ids = idGenerator('row');…What does this promise chain log, and which
thenorcatchhandles each error?Promise.resolve(1) .then((x) => { throw new Error(`boom ${x}`); }) .then((x) => console.log('a', x)) .catch((err) => err.message.toUpperCase()) .then( (x) => { console.log('b', x); throw new Error('again'); }, (err) => console.log('c', err.message), ) .finally(() => console.log('d')) .catch((err) => console.log('e', err.message));What is currying, and how would you implement a
curryfunction that supports bothsum(1)(2)(3)andsum(1, 2)(3)?function curry(fn) { return function curried(...args) { if (args.length >= fn.length) return fn.apply(this, args); return (...more) => curried.apply(this, [...args, ...more]); };}…How would you write a
memoizefunction, and how do you stop its cache from growing forever?function memoize(fn, { maxSize = 100, key = (...args) => JSON.stringify(args) } = {}) { const cache = new Map(); // a Map remembers insertion order: the first key is the oldest return function (...args) { const k = key(...args); if (cache.has(k)) {…How would you implement an
EventEmitterwithon,off,emitandonce?class EventEmitter { #listeners = new Map(); // event name -> array of listeners on(event, listener) { this.#listeners.set(event, [...(this.#listeners.get(event) ?? []), listener]); return () => this.off(event, listener); // handy as a useEffect cleanup…How would you implement
Promise.allfrom scratch, keeping the result order and rejecting on the first error?function promiseAll(iterable) { return new Promise((resolve, reject) => { const items = Array.from(iterable); const results = new Array(items.length); let pending = items.length; if (pending === 0) return resolve(results);…How would you write
retry(fn, attempts)that retries a failing async call with exponential backoff?const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)); async function retry(fn, { attempts = 3, baseDelay = 200, maxDelay = 5000, shouldRetry = () => true } = {}) { for (let attempt = 1; ; attempt++) { try { return await fn(attempt); // without await, a rejection would skip this catch…How would you run a list of async tasks with at most N in flight at once (a promise pool) and keep the results in order?
async function runPool(tasks, limit) { const results = new Array(tasks.length); let next = 0; // shared cursor: safe, because next++ runs without an await in between async function worker() { while (next < tasks.length) {…What is a
Proxy, how do libraries like Immer, MobX and Vue use it, and what does it cost?// a tiny Vue-style reactive object: record reads, react to writesfunction reactive(target, onChange) { return new Proxy(target, { get(obj, key, receiver) { console.log('read', String(key)); // Vue's track(): remember who depends on key return Reflect.get(obj, key, receiver);…How does garbage collection work in a JavaScript engine like V8, and how can a closure keep a large object alive by accident?
// run with: node --expose-gc gc.jsconst handlers = []; function setup() { const report = new Array(1_000_000).fill(0); // about 8 MB const total = () => report.length; // the only closure that reads report…