javascript

examples

examples.js
/**
 * ============================================
 * 6.5 MEMORY MANAGEMENT - EXAMPLES
 * ============================================
 */

/**
 * EXAMPLE 1: Stack vs Heap - Primitives
 * -------------------------------------
 * Primitives are copied by value
 */

console.log('=== Example 1: Primitives (Stack) ===');

let a = 10;
let b = a; // Copy of value

b = 20;

console.log('a:', a); // 10 (unchanged)
console.log('b:', b); // 20

// Each variable has its own copy in stack memory

/**
 * EXAMPLE 2: Stack vs Heap - Objects
 * ----------------------------------
 * Objects are passed by reference
 */

console.log('\n=== Example 2: Objects (Heap) ===');

let obj1 = { value: 10 };
let obj2 = obj1; // Copy of reference (same object!)

obj2.value = 20;

console.log('obj1.value:', obj1.value); // 20 (changed!)
console.log('obj2.value:', obj2.value); // 20

console.log('obj1 === obj2:', obj1 === obj2); // true (same reference)

/**
 * EXAMPLE 3: Creating a Copy
 * --------------------------
 * How to avoid sharing references
 */

console.log('\n=== Example 3: Creating Copies ===');

const original = { name: 'Alice', age: 30 };

// Shallow copy
const shallowCopy = { ...original };
shallowCopy.name = 'Bob';

console.log('original.name:', original.name); // "Alice"
console.log('shallowCopy.name:', shallowCopy.name); // "Bob"

// Deep copy (for nested objects)
const nested = { user: { name: 'Alice' } };
const deepCopy = JSON.parse(JSON.stringify(nested));
deepCopy.user.name = 'Bob';

console.log('nested.user.name:', nested.user.name); // "Alice"
console.log('deepCopy.user.name:', deepCopy.user.name); // "Bob"

/**
 * EXAMPLE 4: Garbage Collection Basics
 * ------------------------------------
 * Objects are collected when unreachable
 */

console.log('\n=== Example 4: Garbage Collection ===');

function createObject() {
  const localObj = { data: new Array(1000).fill('x') };
  console.log('Object created with', localObj.data.length, 'items');
  // localObj goes out of scope here - eligible for GC
}

createObject();
console.log('Function returned - object can be garbage collected');

/**
 * EXAMPLE 5: Reference Keeping Object Alive
 * -----------------------------------------
 * Object stays in memory if referenced
 */

console.log('\n=== Example 5: Keeping Objects Alive ===');

let keepAlive = null;

function createAndKeep() {
  const obj = { data: 'important data' };
  keepAlive = obj; // Reference kept!
  console.log('Object created and referenced');
}

createAndKeep();
console.log('Function returned but object still alive:', keepAlive.data);

// To allow GC:
// keepAlive = null;

/**
 * EXAMPLE 6: Memory Leak - Accidental Global
 * ------------------------------------------
 * Variables without declaration become global
 */

console.log('\n=== Example 6: Accidental Global ===');

function leakyFunction() {
  // In non-strict mode, this creates a global variable!
  // leakedVar = "I'm accidentally global";

  // CORRECT way:
  let properVar = "I'm properly scoped";
  console.log('Proper variable:', properVar);
}

leakyFunction();
// console.log(leakedVar); // Would exist if we created the leak!

/**
 * EXAMPLE 7: Memory Leak - Forgotten Timer
 * ----------------------------------------
 * Timers keep references alive
 */

console.log('\n=== Example 7: Timer Leak ===');

function timerLeak() {
  const largeData = new Array(10000).fill('data');

  // This timer keeps largeData in memory!
  const timerId = setInterval(() => {
    console.log('Timer running, data length:', largeData.length);
  }, 1000);

  // Return cleanup function
  return () => {
    clearInterval(timerId);
    console.log('Timer cleaned up');
  };
}

const cleanup = timerLeak();
// Call cleanup() when done to prevent leak
setTimeout(cleanup, 2500); // Clean up after a few ticks

/**
 * EXAMPLE 8: Closure Memory
 * -------------------------
 * Closures can hold more memory than expected
 */

console.log('\n=== Example 8: Closure Memory ===');

function createClosures() {
  const hugeArray = new Array(100000).fill('x');
  const smallValue = hugeArray.length;

  // BAD: Keeps entire array in memory
  const badClosure = () => {
    return hugeArray[0];
  };

  // GOOD: Only keeps what's needed
  const goodClosure = () => {
    return smallValue;
  };

  return { badClosure, goodClosure };
}

const closures = createClosures();
console.log('Bad closure result:', closures.badClosure());
console.log('Good closure result:', closures.goodClosure());
// badClosure keeps the 100k array alive!

/**
 * EXAMPLE 9: WeakMap for Object Metadata
 * --------------------------------------
 * WeakMap allows garbage collection of keys
 */

console.log('\n=== Example 9: WeakMap ===');

const metadata = new WeakMap();

function processObject(obj) {
  if (!metadata.has(obj)) {
    metadata.set(obj, {
      processedAt: Date.now(),
      count: 0,
    });
  }

  const data = metadata.get(obj);
  data.count++;

  return data;
}

let myObj = { name: 'test' };
console.log('First process:', processObject(myObj));
console.log('Second process:', processObject(myObj));

// When myObj is set to null, the metadata is also GC'd
// myObj = null;

/**
 * EXAMPLE 10: WeakSet for Tracking
 * --------------------------------
 * Track objects without preventing GC
 */

console.log('\n=== Example 10: WeakSet ===');

const processed = new WeakSet();

function processOnce(obj) {
  if (processed.has(obj)) {
    console.log('Already processed:', obj.id);
    return false;
  }

  processed.add(obj);
  console.log('Processing:', obj.id);
  return true;
}

const item1 = { id: 1 };
const item2 = { id: 2 };

processOnce(item1); // Processing: 1
processOnce(item2); // Processing: 2
processOnce(item1); // Already processed: 1

/**
 * EXAMPLE 11: Object Pooling
 * --------------------------
 * Reuse objects to reduce allocations
 */

console.log('\n=== Example 11: Object Pooling ===');

class ParticlePool {
  constructor(size) {
    this.particles = [];
    for (let i = 0; i < size; i++) {
      this.particles.push({
        x: 0,
        y: 0,
        vx: 0,
        vy: 0,
        active: false,
      });
    }
    console.log(`Pool created with ${size} particles`);
  }

  acquire() {
    const particle = this.particles.find((p) => !p.active);
    if (particle) {
      particle.active = true;
      return particle;
    }
    console.log('Pool exhausted!');
    return null;
  }

  release(particle) {
    particle.x = 0;
    particle.y = 0;
    particle.vx = 0;
    particle.vy = 0;
    particle.active = false;
  }

  getActiveCount() {
    return this.particles.filter((p) => p.active).length;
  }
}

const pool = new ParticlePool(5);

const p1 = pool.acquire();
p1.x = 100;
const p2 = pool.acquire();
p2.x = 200;

console.log('Active particles:', pool.getActiveCount());

pool.release(p1);
console.log('After release:', pool.getActiveCount());

/**
 * EXAMPLE 12: Nullifying References
 * ---------------------------------
 * Explicitly clear large objects
 */

console.log('\n=== Example 12: Nullifying References ===');

function processLargeData() {
  let data = new Array(100000).fill('important');

  // Process the data
  const result = data.length;

  // Clear the reference
  data = null;

  console.log('Data processed, reference cleared');
  return result;
}

console.log('Result:', processLargeData());

/**
 * EXAMPLE 13: Event Listener Cleanup
 * ----------------------------------
 * Properly remove event listeners
 */

console.log('\n=== Example 13: Event Listener Cleanup ===');

class Component {
  constructor(id) {
    this.id = id;
    this.data = new Array(1000).fill('data');
    this.boundHandler = this.handleEvent.bind(this);

    // Simulate adding listener
    console.log(`Component ${id}: Adding event listener`);
    // In browser: element.addEventListener('click', this.boundHandler);
  }

  handleEvent(event) {
    console.log(`Component ${this.id}: Handling event`);
  }

  destroy() {
    // Remove listener to prevent leak
    console.log(`Component ${this.id}: Removing event listener`);
    // In browser: element.removeEventListener('click', this.boundHandler);
    this.data = null;
  }
}

const comp = new Component(1);
// When done:
comp.destroy();

/**
 * EXAMPLE 14: Chunked Processing
 * ------------------------------
 * Process data in chunks to manage memory
 */

console.log('\n=== Example 14: Chunked Processing ===');

async function processInChunks(items, chunkSize, processor) {
  console.log(`Processing ${items.length} items in chunks of ${chunkSize}`);

  for (let i = 0; i < items.length; i += chunkSize) {
    const chunk = items.slice(i, i + chunkSize);

    // Process chunk
    for (const item of chunk) {
      processor(item);
    }

    console.log(
      `Processed items ${i} to ${Math.min(i + chunkSize, items.length)}`
    );

    // Yield to allow GC and other operations
    await new Promise((resolve) => setTimeout(resolve, 0));
  }

  console.log('All chunks processed');
}

const items = Array.from({ length: 100 }, (_, i) => i);
processInChunks(items, 25, (item) => {
  // Process each item
});

/**
 * EXAMPLE 15: Measuring Memory (Node.js)
 * --------------------------------------
 * Check memory usage
 */

console.log('\n=== Example 15: Memory Measurement ===');

function getMemoryUsage() {
  if (typeof process !== 'undefined' && process.memoryUsage) {
    const usage = process.memoryUsage();
    return {
      heapUsed: Math.round(usage.heapUsed / 1024 / 1024) + ' MB',
      heapTotal: Math.round(usage.heapTotal / 1024 / 1024) + ' MB',
    };
  }
  return 'Memory API not available (browser)';
}

console.log('Memory before:', getMemoryUsage());

// Allocate some memory
const bigArray = new Array(1000000).fill('x');
console.log('Memory after allocation:', getMemoryUsage());

// Clear reference
// bigArray = null;
// In Node.js with --expose-gc flag, you could call global.gc()

/**
 * EXAMPLE 16: Circular References
 * -------------------------------
 * Modern GC handles circular references
 */

console.log('\n=== Example 16: Circular References ===');

function createCircular() {
  const objA = { name: 'A' };
  const objB = { name: 'B' };

  // Circular reference
  objA.ref = objB;
  objB.ref = objA;

  console.log('Created circular reference');
  return { objA, objB };
}

const circular = createCircular();
console.log('A references B:', circular.objA.ref.name);
console.log('B references A:', circular.objB.ref.name);

// Modern GC (mark-and-sweep) can handle this!
// Both objects will be collected when 'circular' is nulled

/**
 * EXAMPLE 17: Memory-Efficient Strings
 * ------------------------------------
 * String interning and memory
 */

console.log('\n=== Example 17: String Memory ===');

// Identical strings may share memory (interning)
const str1 = 'hello';
const str2 = 'hello';
console.log('str1 === str2:', str1 === str2); // true, possibly same memory

// But string operations create new strings
const str3 = 'hel' + 'lo';
console.log('str1 === str3:', str1 === str3); // true, engine may optimize

// Large string concatenation - use array join
const parts = [];
for (let i = 0; i < 1000; i++) {
  parts.push(`item${i}`);
}
const result = parts.join(', ');
console.log('Joined string length:', result.length);

/**
 * EXAMPLE 18: Map vs Object Memory
 * --------------------------------
 * Different memory characteristics
 */

console.log('\n=== Example 18: Map vs Object ===');

// Objects: Good for fixed known keys
const objStore = {
  key1: 'value1',
  key2: 'value2',
};

// Maps: Better for dynamic keys, more memory for metadata
const mapStore = new Map([
  ['key1', 'value1'],
  ['key2', 'value2'],
]);

// WeakMap: Keys can be garbage collected
const weakStore = new WeakMap();
let key = { id: 1 };
weakStore.set(key, 'value');

console.log('Object keys:', Object.keys(objStore));
console.log('Map size:', mapStore.size);
console.log('WeakMap has key:', weakStore.has(key));

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