JavaScript Detailed Course#
Table of Contents#
- V8 Engine Architecture & JIT Compilation
- The Event Loop, Microtasks & Macrotasks
- Memory Management & Garbage Collection
- Prototypes & Prototypal Inheritance
- Execution Contexts & Lexical Scope
- The 'this' Keyword: All 4 Binding Rules
- Async Generators & Web Workers
- Metaprogramming: Proxy, Reflect & Symbols
- Browser Pipeline & Event Bubbling/Capturing
- 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
- Parser: Reads JavaScript characters, verifies syntax, and generates an Abstract Syntax Tree (AST).
- Ignition (Interpreter): Generates compact bytecodes from the AST and executes them immediately with fast startup time. While executing, it collects type feedback profiling data.
- 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.
- 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:
- Call Stack: Executes current synchronous stack frames (LIFO).
- Microtask Queue:
Promise.then/catch/finallycallbacks,queueMicrotask(),MutationObserver, andprocess.nextTick(Node). Every single microtask in this queue is drained to completion before the browser renders or moves to any macrotask! - 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:
- Mark-and-Sweep Algorithm: The GC starts from "roots" (global object, local variables in current call stack) and marks all reachable references. Unreachable objects are swept and reclaimed.
- Generational Collection: The heap is partitioned into New Space (short-lived objects, collected frequently via Scavenge) and Old Space (objects that survived multiple GC cycles).
Top Causes of Memory Leaks in JavaScript
- Accidental Globals: Assigning to undeclared variables attaches them to the
window/globalroot. - Forgotten Timers / Callbacks: Running
setIntervalthat captures closures without callingclearInterval. - Detached DOM Elements: Holding JavaScript references in an array or map to DOM elements that have already been removed from the document.
- 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:
- Variable Environment: Stores
vardeclarations and function declarations. - Lexical Environment: Stores
letandconstbindings, and an outer reference link pointing to its parent scope. - Temporal Dead Zone (TDZ): The time between entering a scope and when a
letorconstvariable is formally declared. Accessing the variable in the TDZ throws aReferenceError.
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:
- Default Binding: Standalone invocation
fn()binds to the global object (orundefinedin strict mode). - Implicit Binding: Invoked as a method
obj.fn()bindsthistoobj. - Explicit Binding:
fn.call(context, ...args),fn.apply(context, [args]), orfn.bind(context)explicitly setsthis. - New Binding: When called with
new fn(), a fresh object is created, linked tofn.prototype, and bound tothis.
Note on Arrow Functions: Arrow functions do not have their own
thisbinding. They resolvethislexically 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:
- Capturing Phase (Trickle down): Travels from
window→document→<html>down to the target element. - Target Phase: Triggers listeners registered directly on the element.
- 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.