JavaScript Detailed Course#

Table of Contents#

  1. V8 Engine Architecture & JIT Compilation
  2. The Event Loop, Microtasks & Macrotasks
  3. Memory Management & Garbage Collection
  4. Prototypes & Prototypal Inheritance
  5. Execution Contexts & Lexical Scope
  6. The 'this' Keyword: All 4 Binding Rules
  7. Async Generators & Web Workers
  8. Metaprogramming: Proxy, Reflect & Symbols
  9. Browser Pipeline & Event Bubbling/Capturing
  10. Performance Optimization & Interview Deep Dive

1. V8 Engine Architecture & JIT Compilation#

Modern JavaScript engines (Google V8, JavaScriptCore in WebKit, SpiderMonkey in Firefox) are hybrid compiler/interpreters that convert raw source code into high-speed machine instructions:

V8 Pipeline Stages

  1. Parser: Reads JavaScript characters, verifies syntax, and generates an Abstract Syntax Tree (AST).
  2. Ignition (Interpreter): Generates compact bytecodes from the AST and executes them immediately with fast startup time. While executing, it collects type feedback profiling data.
  3. TurboFan (Optimizing JIT Compiler): Functions that are called repeatedly ("hot functions") with stable parameter types are compiled by TurboFan directly into optimized native machine code.
  4. Deoptimization (Bailout): If a hot function receives an unexpected type (e.g. passing a string to a function that previously only saw integers), TurboFan discards the optimized code and bails back to Ignition bytecode.

2. The Event Loop, Microtasks & Macrotasks#

JavaScript executes synchronously on a single thread. Asynchrony is orchestrated via queues coordinated by the Event Loop:

  1. Call Stack: Executes current synchronous stack frames (LIFO).
  2. Microtask Queue: Promise.then / catch / finally callbacks, queueMicrotask(), MutationObserver, and process.nextTick (Node). Every single microtask in this queue is drained to completion before the browser renders or moves to any macrotask!
  3. Macrotask (Task) Queue: setTimeout, setInterval, setImmediate, I/O events, and UI rendering tasks. Only one macrotask is processed per loop tick.
console.log("1. Synchronous");

setTimeout(() => {
  console.log("4. Macrotask (setTimeout)");
}, 0);

Promise.resolve().then(() => {
  console.log("2. Microtask 1");
}).then(() => {
  console.log("3. Microtask 2");
});

console.log("Synchronous end");

// Output Order:
// 1. Synchronous
// Synchronous end
// 2. Microtask 1
// 3. Microtask 2
// 4. Macrotask (setTimeout)

3. Memory Management & Garbage Collection#

JavaScript manages memory automatically via garbage collection, allocating primitive values on the stack and objects on the memory heap:

Top Causes of Memory Leaks in JavaScript

  1. Accidental Globals: Assigning to undeclared variables attaches them to the window / global root.
  2. Forgotten Timers / Callbacks: Running setInterval that captures closures without calling clearInterval.
  3. Detached DOM Elements: Holding JavaScript references in an array or map to DOM elements that have already been removed from the document.
  4. Closures Capturing Large Scopes: An unused inner function keeping large objects in memory because it shares the parent lexical environment.

4. Prototypes & Prototypal Inheritance#

Every object in JavaScript has an internal link to another object called its prototype. When querying a property, JavaScript searches the object itself; if not found, it traverses up the prototype chain until reaching Object.prototype or null.

// Pure Prototypal Inheritance
const animal = {
  makeSound() {
    return `${this.name} makes a sound.`;
  }
};

const dog = Object.create(animal);
dog.name = "Rex";
dog.bark = function() { return "Woof!"; };

console.log(dog.makeSound()); // "Rex makes a sound." (found on prototype!)
console.log(Object.getPrototypeOf(dog) === animal); // true

// Modern ES6 Class syntax is syntactic sugar over prototype chains:
class Animal {
  constructor(name) { this.name = name; }
  makeSound() { return `${this.name} speaks.`; }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);
    this.breed = breed;
  }
}
const myDog = new Dog("Buddy", "Golden Retriever");
console.log(myDog instanceof Animal); // true

5. Execution Contexts & Lexical Scope#

An Execution Context is the environment in which JavaScript code is evaluated. Each context contains:

6. The 'this' Keyword: All 4 Binding Rules#

The value of this depends entirely on how a function is invoked, determined by 4 precedence rules:

  1. Default Binding: Standalone invocation fn() binds to the global object (or undefined in strict mode).
  2. Implicit Binding: Invoked as a method obj.fn() binds this to obj.
  3. Explicit Binding: fn.call(context, ...args), fn.apply(context, [args]), or fn.bind(context) explicitly sets this.
  4. New Binding: When called with new fn(), a fresh object is created, linked to fn.prototype, and bound to this.

Note on Arrow Functions: Arrow functions do not have their own this binding. They resolve this lexically from their enclosing scope.

7. Async Generators & Web Workers#

For high-performance data streaming and CPU-intensive workloads:

// Async Generator: Yields items over time (e.g. streaming LLM responses)
async function* fetchStream(url) {
  const response = await fetch(url);
  const reader = response.body.getReader();
  const decoder = new TextDecoder();

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    yield decoder.decode(value);
  }
}

// Consuming with for-await-of
for await (const chunk of fetchStream("/api/llm-stream")) {
  process.stdout.write(chunk);
}

8. Metaprogramming: Proxy, Reflect & Symbols#

Proxy enables intercepting and customizing fundamental language operations (property lookup, assignment, enumeration, function invocation):

const state = { count: 0 };

const reactiveState = new Proxy(state, {
  get(target, prop, receiver) {
    console.log(`[Read] property: ${String(prop)}`);
    return Reflect.get(target, prop, receiver);
  },
  set(target, prop, value, receiver) {
    console.log(`[Mutation] ${String(prop)} = ${value}`);
    // Trigger reactive UI re-render here (Vue 3 reactivity core model)
    return Reflect.set(target, prop, value, receiver);
  }
});

reactiveState.count = 5; // Triggers [Mutation] count = 5

9. Browser Pipeline & Event Bubbling/Capturing#

When an event occurs in the DOM, it travels in two phases:

  1. Capturing Phase (Trickle down): Travels from windowdocument<html> down to the target element.
  2. Target Phase: Triggers listeners registered directly on the element.
  3. Bubbling Phase (Bubble up): Travels back up from the target to ancestors.

Event Delegation Pattern: Rather than attaching 1,000 click listeners to individual table rows, attach a single listener on the parent container using event.target.closest('tr').

10. Performance Optimization & Interview Deep Dive#

Debounce vs Throttle

// Debounce: Executes once after N milliseconds of inactivity (Search input)
function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}

// Throttle: Executes at most once every N milliseconds (Scroll/Resize)
function throttle(fn, limit) {
  let inThrottle = false;
  return function(...args) {
    if (!inThrottle) {
      fn.apply(this, args);
      inThrottle = true;
      setTimeout(() => inThrottle = false, limit);
    }
  };
}

Next Steps: Review quick syntax and collections in the JavaScript Crash Course, explore algorithms in DSA Detailed Course, or return to the TechToday Homepage.