javascript

examples

examples.js⚔
/**
 * ============================================================
 * 22.2 HIDDEN CLASSES & INLINE CACHING - EXAMPLES
 * ============================================================
 *
 * Demonstrations of hidden class behavior and inline caching.
 */

/**
 * ============================================================
 * EXAMPLE 1: SHARED HIDDEN CLASSES
 * ============================================================
 */

console.log('=== Example 1: Shared Hidden Classes ===\n');

// All these objects share the same hidden class
function createPoint(x, y) {
  return { x, y };
}

const p1 = createPoint(1, 2);
const p2 = createPoint(3, 4);
const p3 = createPoint(5, 6);

console.log('Three points with same structure:');
console.log('p1:', p1);
console.log('p2:', p2);
console.log('p3:', p3);
console.log('These share the same hidden class (x @ offset 0, y @ offset 8)\n');

/**
 * ============================================================
 * EXAMPLE 2: HIDDEN CLASS TRANSITIONS
 * ============================================================
 */

console.log('=== Example 2: Hidden Class Transitions ===\n');

// Watch how hidden class changes as properties are added
const obj = {};
console.log('Step 1: Empty object {}');
console.log('  Hidden Class: C0 (empty)');

obj.a = 1;
console.log("Step 2: Added 'a':", obj);
console.log("  Transition: C0 → C1 (has 'a' @ offset 0)");

obj.b = 2;
console.log("Step 3: Added 'b':", obj);
console.log("  Transition: C1 → C2 (has 'a' @ 0, 'b' @ 8)");

obj.c = 3;
console.log("Step 4: Added 'c':", obj);
console.log("  Transition: C2 → C3 (has 'a' @ 0, 'b' @ 8, 'c' @ 16)\n");

/**
 * ============================================================
 * EXAMPLE 3: PROPERTY ORDER MATTERS
 * ============================================================
 */

console.log('=== Example 3: Property Order Matters ===\n');

// These have DIFFERENT hidden classes despite same properties
const orderA = {};
orderA.x = 1;
orderA.y = 2;

const orderB = {};
orderB.y = 2; // Different order!
orderB.x = 1;

console.log('orderA (x then y):', orderA);
console.log('orderB (y then x):', orderB);
console.log('\nāš ļø These have DIFFERENT hidden classes!');
console.log('orderA: x @ offset 0, y @ offset 8');
console.log('orderB: y @ offset 0, x @ offset 8\n');

// Demonstrating the issue
function getX(obj) {
  return obj.x;
}

// If we call getX with both, it becomes polymorphic
console.log('getX(orderA):', getX(orderA), '- IC sees shape A');
console.log('getX(orderB):', getX(orderB), '- IC now polymorphic (2 shapes)\n');

/**
 * ============================================================
 * EXAMPLE 4: CONSISTENT OBJECT CREATION
 * ============================================================
 */

console.log('=== Example 4: Consistent Object Creation ===\n');

// āŒ BAD: Inconsistent creation
function createUserBad(data) {
  const user = {};

  if (data.name) user.name = data.name;
  if (data.email) user.email = data.email;
  if (data.age) user.age = data.age;

  return user;
}

// These all have different hidden classes!
const user1 = createUserBad({ name: 'Alice' });
const user2 = createUserBad({ name: 'Bob', email: 'bob@test.com' });
const user3 = createUserBad({
  age: 25,
  name: 'Carol',
  email: 'carol@test.com',
});

console.log('āŒ BAD: Different shapes');
console.log('user1:', user1, '- Shape: {name}');
console.log('user2:', user2, '- Shape: {name, email}');
console.log('user3:', user3, '- Shape: {age, name, email}');

// āœ… GOOD: Consistent creation
function createUserGood(data) {
  return {
    name: data.name || null,
    email: data.email || null,
    age: data.age || null,
  };
}

const goodUser1 = createUserGood({ name: 'Alice' });
const goodUser2 = createUserGood({ name: 'Bob', email: 'bob@test.com' });
const goodUser3 = createUserGood({
  age: 25,
  name: 'Carol',
  email: 'carol@test.com',
});

console.log('\nāœ… GOOD: Same shape (all have name, email, age)');
console.log('goodUser1:', goodUser1);
console.log('goodUser2:', goodUser2);
console.log('goodUser3:', goodUser3);
console.log();

/**
 * ============================================================
 * EXAMPLE 5: INLINE CACHING STATES
 * ============================================================
 */

console.log('=== Example 5: Inline Caching States ===\n');

// MONOMORPHIC: Only one shape
function getValue_Mono(obj) {
  return obj.value;
}

const monoObjects = [];
for (let i = 0; i < 1000; i++) {
  monoObjects.push({ value: i }); // All same shape
}

console.log('MONOMORPHIC example:');
console.log('All 1000 objects have same shape: { value }');
monoObjects.forEach((o) => getValue_Mono(o));
console.log('IC state: MONOMORPHIC (fastest)\n');

// POLYMORPHIC: 2-4 shapes
function getValue_Poly(obj) {
  return obj.value;
}

console.log('POLYMORPHIC example:');
getValue_Poly({ value: 1 });
console.log('Call 1: { value } - IC: MONOMORPHIC');
getValue_Poly({ value: 2, extra: 'a' });
console.log('Call 2: { value, extra } - IC: POLYMORPHIC (2 shapes)');
getValue_Poly({ other: true, value: 3 });
console.log('Call 3: { other, value } - IC: POLYMORPHIC (3 shapes)');
getValue_Poly({ value: 4, x: 1, y: 2 });
console.log('Call 4: { value, x, y } - IC: POLYMORPHIC (4 shapes)\n');

// MEGAMORPHIC: 5+ shapes
function getValue_Mega(obj) {
  return obj.value;
}

console.log('MEGAMORPHIC example:');
getValue_Mega({ value: 1 });
getValue_Mega({ value: 2, a: 1 });
getValue_Mega({ value: 3, b: 1 });
getValue_Mega({ value: 4, c: 1 });
getValue_Mega({ value: 5, d: 1 });
getValue_Mega({ value: 6, e: 1 }); // 6th shape!
console.log('Called with 6 different shapes');
console.log('IC state: MEGAMORPHIC (slow - dictionary lookup)\n');

/**
 * ============================================================
 * EXAMPLE 6: PERFORMANCE IMPACT DEMONSTRATION
 * ============================================================
 */

console.log('=== Example 6: Performance Impact ===\n');

// Create test data
const ITERATIONS = 1000000;

// Monomorphic test data - all same shape
const monoData = [];
for (let i = 0; i < 1000; i++) {
  monoData.push({ x: i, y: i * 2 });
}

// Polymorphic test data - 4 shapes
const polyData = [];
for (let i = 0; i < 250; i++) {
  polyData.push({ x: i });
  polyData.push({ x: i, a: 1 });
  polyData.push({ x: i, b: 2 });
  polyData.push({ x: i, c: 3 });
}

// Megamorphic test data - many shapes
const megaData = [];
for (let i = 0; i < 100; i++) {
  for (let j = 0; j < 10; j++) {
    const obj = { x: i };
    obj['prop' + j] = j; // Different property each time
    megaData.push(obj);
  }
}

function accessProperty(obj) {
  return obj.x;
}

// Warmup
monoData.forEach(accessProperty);

// Benchmark monomorphic
const startMono = performance.now();
for (let i = 0; i < ITERATIONS; i++) {
  accessProperty(monoData[i % monoData.length]);
}
const monoTime = performance.now() - startMono;

// Reset IC by creating new function
function accessPropertyPoly(obj) {
  return obj.x;
}

// Warmup polymorphic
polyData.forEach(accessPropertyPoly);

// Benchmark polymorphic
const startPoly = performance.now();
for (let i = 0; i < ITERATIONS; i++) {
  accessPropertyPoly(polyData[i % polyData.length]);
}
const polyTime = performance.now() - startPoly;

// Reset IC
function accessPropertyMega(obj) {
  return obj.x;
}

// Warmup megamorphic
megaData.forEach(accessPropertyMega);

// Benchmark megamorphic
const startMega = performance.now();
for (let i = 0; i < ITERATIONS; i++) {
  accessPropertyMega(megaData[i % megaData.length]);
}
const megaTime = performance.now() - startMega;

console.log(`MONOMORPHIC:  ${monoTime.toFixed(2)}ms (baseline)`);
console.log(
  `POLYMORPHIC:  ${polyTime.toFixed(2)}ms (${(polyTime / monoTime).toFixed(
    2
  )}x slower)`
);
console.log(
  `MEGAMORPHIC:  ${megaTime.toFixed(2)}ms (${(megaTime / monoTime).toFixed(
    2
  )}x slower)`
);
console.log();

/**
 * ============================================================
 * EXAMPLE 7: DELETE OPERATOR IMPACT
 * ============================================================
 */

console.log('=== Example 7: Delete Operator Impact ===\n');

// āŒ Using delete - causes dictionary mode
function createWithDeleteBad() {
  const obj = { a: 1, b: 2, c: 3, temp: 'temporary' };
  delete obj.temp; // Transitions to slow mode!
  return obj;
}

// āœ… Using null instead
function createWithNullGood() {
  const obj = { a: 1, b: 2, c: 3, temp: 'temporary' };
  obj.temp = null; // Keeps fast mode
  return obj;
}

// āœ… Never adding temporary property
function createOptimalBest() {
  const obj = { a: 1, b: 2, c: 3 };
  // Process temp separately without adding to object
  return obj;
}

const objWithDelete = createWithDeleteBad();
const objWithNull = createWithNullGood();
const objOptimal = createOptimalBest();

console.log('After delete:', objWithDelete, '- āš ļø Slow (dictionary mode)');
console.log('With null:', objWithNull, '- āœ… Fast (keeps shape)');
console.log('Optimal:', objOptimal, '- āœ… Best (no extra property)\n');

// Performance test
function accessProps(obj) {
  return obj.a + obj.b + obj.c;
}

const withDeleteArr = Array.from({ length: 1000 }, () => createWithDeleteBad());
const withNullArr = Array.from({ length: 1000 }, () => createWithNullGood());

// Warmup
withDeleteArr.forEach(accessProps);
withNullArr.forEach(accessProps);

const iters = 100000;

const startDelete = performance.now();
for (let i = 0; i < iters; i++) {
  accessProps(withDeleteArr[i % 1000]);
}
const deleteTime = performance.now() - startDelete;

const startNull = performance.now();
for (let i = 0; i < iters; i++) {
  accessProps(withNullArr[i % 1000]);
}
const nullTime = performance.now() - startNull;

console.log('Property access performance:');
console.log(`With delete:    ${deleteTime.toFixed(2)}ms`);
console.log(`With null:      ${nullTime.toFixed(2)}ms`);
console.log(`delete is ${(deleteTime / nullTime).toFixed(2)}x slower\n`);

/**
 * ============================================================
 * EXAMPLE 8: CLASS-BASED CONSISTENT SHAPES
 * ============================================================
 */

console.log('=== Example 8: Classes for Consistent Shapes ===\n');

// Classes ensure consistent hidden classes
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
}

class Point3D extends Point {
  constructor(x, y, z) {
    super(x, y);
    this.z = z;
  }
}

const classPoints = [];
for (let i = 0; i < 1000; i++) {
  classPoints.push(new Point(i, i * 2));
}

console.log('1000 Point instances created');
console.log('All share same hidden class from constructor');
console.log('Sample:', classPoints[0], classPoints[500], classPoints[999]);

// Method access is also optimized
class Counter {
  constructor(initial = 0) {
    this.value = initial;
  }

  increment() {
    return ++this.value;
  }

  decrement() {
    return --this.value;
  }
}

const counters = Array.from({ length: 100 }, (_, i) => new Counter(i));
console.log('\n100 Counter instances - all same shape');
counters[0].increment();
counters[1].increment();
console.log('Counter[0]:', counters[0].value);
console.log('Counter[1]:', counters[1].value);
console.log();

/**
 * ============================================================
 * EXAMPLE 9: MAP FOR DYNAMIC KEYS
 * ============================================================
 */

console.log('=== Example 9: Map for Dynamic Keys ===\n');

// āŒ BAD: Dynamic keys on objects
function cacheWithObjectBad() {
  const cache = {};

  // Each unique key creates a hidden class transition!
  for (let i = 0; i < 100; i++) {
    cache['key_' + i] = 'value_' + i;
  }

  return cache;
}

// āœ… GOOD: Use Map for dynamic keys
function cacheWithMapGood() {
  const cache = new Map();

  // Map handles dynamic keys efficiently
  for (let i = 0; i < 100; i++) {
    cache.set('key_' + i, 'value_' + i);
  }

  return cache;
}

console.log('Object cache with 100 dynamic keys:');
const objCache = cacheWithObjectBad();
console.log('  Keys:', Object.keys(objCache).length);
console.log('  āš ļø Hidden class transitioned 100 times!');

console.log('\nMap cache with 100 dynamic keys:');
const mapCache = cacheWithMapGood();
console.log('  Size:', mapCache.size);
console.log('  āœ… No hidden class issues - Maps handle this natively\n');

// Performance comparison
const CACHE_ITERS = 100000;

const objCacheTest = {};
const mapCacheTest = new Map();

// Warmup
for (let i = 0; i < 1000; i++) {
  objCacheTest['warmup' + i] = i;
  mapCacheTest.set('warmup' + i, i);
}

// Object set performance
const startObjSet = performance.now();
for (let i = 0; i < CACHE_ITERS; i++) {
  objCacheTest['test' + (i % 100)] = i;
}
const objSetTime = performance.now() - startObjSet;

// Map set performance
const startMapSet = performance.now();
for (let i = 0; i < CACHE_ITERS; i++) {
  mapCacheTest.set('test' + (i % 100), i);
}
const mapSetTime = performance.now() - startMapSet;

console.log('Dynamic key set performance:');
console.log(`Object: ${objSetTime.toFixed(2)}ms`);
console.log(`Map:    ${mapSetTime.toFixed(2)}ms\n`);

/**
 * ============================================================
 * EXAMPLE 10: REAL-WORLD PATTERN: API RESPONSES
 * ============================================================
 */

console.log('=== Example 10: Real-World API Response Handling ===\n');

// Simulated API responses with different structures
const apiResponses = [
  { id: 1, name: 'Product A', price: 100 },
  { id: 2, name: 'Product B', price: 200, discount: 10 },
  { id: 3, name: 'Product C', price: 150, discount: 5, featured: true },
  { id: 4, name: 'Product D', price: 300 },
  { id: 5, name: 'Product E', price: 250, featured: true },
];

console.log('āŒ Raw API responses - inconsistent shapes:');
apiResponses.forEach((r, i) => {
  console.log(`  Response ${i + 1}:`, Object.keys(r).join(', '));
});

// Normalize to consistent shape
function normalizeProduct(raw) {
  return {
    id: raw.id,
    name: raw.name,
    price: raw.price,
    discount: raw.discount ?? 0,
    featured: raw.featured ?? false,
  };
}

const normalizedProducts = apiResponses.map(normalizeProduct);

console.log('\nāœ… Normalized products - consistent shapes:');
normalizedProducts.forEach((p, i) => {
  console.log(`  Product ${i + 1}:`, Object.keys(p).join(', '));
});

// Now all products share the same hidden class
function calculateFinalPrice(product) {
  const discount = product.price * (product.discount / 100);
  return product.price - discount;
}

console.log('\nFinal prices (monomorphic access):');
normalizedProducts.forEach((p) => {
  console.log(`  ${p.name}: $${calculateFinalPrice(p)}`);
});
console.log();

/**
 * ============================================================
 * EXAMPLE 11: CHECKING HIDDEN CLASS SHARING
 * ============================================================
 */

console.log('=== Example 11: Verifying Hidden Class Sharing ===\n');

// Function to test if objects likely share hidden class
// (Not 100% accurate but useful for testing)
function likelySameShape(obj1, obj2) {
  const keys1 = Object.keys(obj1);
  const keys2 = Object.keys(obj2);

  if (keys1.length !== keys2.length) return false;

  for (let i = 0; i < keys1.length; i++) {
    if (keys1[i] !== keys2[i]) return false; // Order matters!
  }

  return true;
}

const a1 = { x: 1, y: 2 };
const a2 = { x: 3, y: 4 };
const b1 = { y: 1, x: 2 }; // Different order
const c1 = { x: 1, y: 2, z: 3 }; // Different properties

console.log('Testing shape similarity:');
console.log(`a1 & a2 (same order):     ${likelySameShape(a1, a2)}`);
console.log(`a1 & b1 (different order): ${likelySameShape(a1, b1)}`);
console.log(`a1 & c1 (different props): ${likelySameShape(a1, c1)}`);
console.log();

/**
 * ============================================================
 * EXAMPLE 12: ARRAY ELEMENT SHAPES
 * ============================================================
 */

console.log('=== Example 12: Arrays of Objects ===\n');

// āœ… GOOD: Homogeneous array
const goodArray = [
  { x: 1, y: 1 },
  { x: 2, y: 2 },
  { x: 3, y: 3 },
];

// āŒ BAD: Heterogeneous array
const badArray = [
  { x: 1, y: 1 },
  { x: 2, y: 2, z: 2 },
  { y: 3, x: 3 },
];

function sumX(arr) {
  let sum = 0;
  for (let i = 0; i < arr.length; i++) {
    sum += arr[i].x;
  }
  return sum;
}

// Benchmark
const largeGood = Array.from({ length: 10000 }, (_, i) => ({ x: i, y: i }));
const largeBad = Array.from({ length: 10000 }, (_, i) => {
  if (i % 3 === 0) return { x: i, y: i };
  if (i % 3 === 1) return { x: i, y: i, z: i };
  return { y: i, x: i };
});

// Warmup
sumX(largeGood);
sumX(largeBad);

const itersArr = 10000;

const startGood = performance.now();
for (let i = 0; i < itersArr; i++) {
  sumX(largeGood);
}
const goodTime = performance.now() - startGood;

const startBad = performance.now();
for (let i = 0; i < itersArr; i++) {
  sumX(largeBad);
}
const badTime = performance.now() - startBad;

console.log('Homogeneous array:');
console.log(`  All elements same shape`);
console.log(`  Time: ${goodTime.toFixed(2)}ms`);

console.log('\nHeterogeneous array:');
console.log(`  Elements have 3 different shapes`);
console.log(`  Time: ${badTime.toFixed(2)}ms`);
console.log(`  ${(badTime / goodTime).toFixed(2)}x slower\n`);

console.log('=== Examples Complete ===\n');
Examples - JavaScript Tutorial | DeepML