3 Object.create / getPrototypeOf
GOAL
Build a zero-copy settings cascade (engine → profile → game) in which every layer STORES only its own overrides and READS the rest through its hidden link — then walk that link by hand with Object.getPrototypeOf.
CONCEPT
Object.create(proto) returns a brand-new object whose [[Prototype]] is `proto`. Nothing gets copied: patch the engine defaults later and every running game picks the change up on its very next read. Object.getPrototypeOf follows exactly one link; a.isPrototypeOf(b) asks "does `a` appear ANYWHERE on b's chain?".
HINT
Own props are the ones YOU wrote onto the object (Object.keys lists only own enumerable ones). Everything else is found by lookup, never by storage.
MIRRORS
Layered configuration in game engines and UI toolkits — built-in defaults → the player's saved profile → per-match settings. A deep-merge helper COPIES each layer into a frozen snapshot; the prototype chain gives the same fallback semantics with zero copies, which is why editing a merged snapshot never flows back upstream while a chained read always sees the newest upstream value.
Run
node 03-object-create-settings-cascade.cjsSource
'use strict';
// ---------------------------------------------------------------------
// PROTO-DOJO · Module 1 / Exercise 3 — Object.create / getPrototypeOf
//
// GOAL: Build a zero-copy settings cascade (engine → profile → game)
// in which every layer STORES only its own overrides and READS
// the rest through its hidden link — then walk that link by
// hand with Object.getPrototypeOf.
// CONCEPT: Object.create(proto) returns a brand-new object whose
// [[Prototype]] is `proto`. Nothing gets copied: patch the
// engine defaults later and every running game picks the
// change up on its very next read. Object.getPrototypeOf
// follows exactly one link; a.isPrototypeOf(b) asks "does `a`
// appear ANYWHERE on b's chain?".
// HINT: Own props are the ones YOU wrote onto the object
// (Object.keys lists only own enumerable ones). Everything
// else is found by lookup, never by storage.
// MIRRORS: Layered configuration in game engines and UI toolkits —
// built-in defaults → the player's saved profile → per-match
// settings. A deep-merge helper COPIES each layer into a
// frozen snapshot; the prototype chain gives the same fallback
// semantics with zero copies, which is why editing a merged
// snapshot never flows back upstream while a chained read
// always sees the newest upstream value.
//
// Run: node 03-object-create-settings-cascade.cjs
// ---------------------------------------------------------------------
const assert = require('node:assert');
const engineDefaults = {
difficulty: 'normal',
volume: 0.8,
jumpKey: 'Space',
fpsCap: 60,
};
// -- TODO 1 -----------------------------------------------------------
// Build profileSettings so it PROTOTYPE-CHAINS to engineDefaults (no spread, no
// Object.assign — the profile has to pick up future engine edits for
// free) and
// OWNS exactly one override: volume 0.5.
const profileSettings = null;
// -- TODO 2 -----------------------------------------------------------
// Build gameSettings chained to profileSettings, owning a single override:
// difficulty 'hard'.
const gameSettings = null;
// -- TODO 3 -----------------------------------------------------------
// Starting FROM gameSettings, climb the chain with Object.getPrototypeOf until
// you arrive at engineDefaults. Your expression may not mention the
// profileSettings / engineDefaults variables directly.
const climbedToEngine = null;
// -- checks -----------------------------------------------------------
assert.ok(profileSettings, 'TODO 1: build profileSettings with Object.create(...)');
assert.strictEqual(Object.getPrototypeOf(profileSettings), engineDefaults,
'TODO 1: the hidden link of profileSettings must point at engineDefaults');
assert.deepStrictEqual(Object.keys(profileSettings), ['volume'],
'TODO 1: only volume is OWN — everything else must be inherited, not copied');
assert.strictEqual(profileSettings.volume, 0.5, 'profile override');
assert.strictEqual(profileSettings.jumpKey, 'Space', 'jumpKey should fall through to the engine defaults');
assert.ok(gameSettings, 'TODO 2: build gameSettings with Object.create(...)');
assert.strictEqual(Object.getPrototypeOf(gameSettings), profileSettings,
'TODO 2: gameSettings chains to profileSettings, not straight to the engine defaults');
assert.strictEqual(gameSettings.difficulty, 'hard', 'own override wins');
assert.strictEqual(gameSettings.volume, 0.5, 'one hop: the profile override beats the engine default');
assert.strictEqual(gameSettings.fpsCap, 60, 'two hops: falls all the way through to the engine defaults');
assert.ok(engineDefaults.isPrototypeOf(gameSettings),
'engineDefaults must sit somewhere on gameSettings\'s chain');
// live cascade — the entire reason to chain instead of copy:
engineDefaults.fpsCap = 144;
assert.strictEqual(gameSettings.fpsCap, 144,
'an engine edit must be visible through the chain instantly — if this fails, you copied instead of chained');
assert.strictEqual(climbedToEngine, engineDefaults,
'TODO 3: two climbs — Object.getPrototypeOf(Object.getPrototypeOf(gameSettings))');
console.log('PASS — 03-object-create-settings-cascade: zero-copy fallback via the chain');