Advanced JS

JavaScript is often called the most misunderstood programming language in the world. It is a language built in ten days that became the foundation of the modern internet. To master it, you must look past the syntax and understand the engine, the memory, and the “weird” behaviors that define its soul.

1. The Engine: Under the Hood of V8

Before writing a single line of code, you must understand how it executes. Most modern environments (Chrome, Node.js) use the V8 Engine.

The Pipeline

  1. Parser: Converts code into an Abstract Syntax Tree (AST).
  2. Ignition (Interpreter): Generates bytecode from the AST. It starts executing almost immediately.
  3. TurboFan (Optimizing Compiler): While the interpreter runs, V8 identifies “Hot” code (functions run frequently). TurboFan compiles this into highly optimized machine code.
  4. De-optimization: If a function’s behavior changes (e.g., passing a string to a function that previously only saw numbers), the engine “de-optimizes” and falls back to the interpreter.

Mastery Tip: Keep your function arguments consistent. If a function always receives the same “shape” of object, V8 can use Inline Caching to skip property lookups.

2. Memory Management: The Heap and The Stack

JavaScript manages memory automatically, but a master knows how to avoid leaks.

The Stack vs. The Heap

  • The Stack: Stores static data whose size is known at compile time (primitive values: numbers, strings, booleans, and references to objects). It follows LIFO (Last In, First Out).
  • The Heap: Stores objects and functions. This is a large, unstructured memory pool.

Garbage Collection (Mark-and-Sweep)

The GC periodically finds objects that are no longer “reachable” from the root (the global object).

  1. Mark: It starts from the roots and marks all objects it can find.
  2. Sweep: It removes everything not marked.

Common Memory Leaks:

  • Accidental Globals: Creating variables without let/const.
  • Forgotten Timers: setInterval running in the background of a closed component.
  • Closures: Holding onto large variables in a scope that isn’t needed.
  • Out-of-DOM references: Keeping a variable reference to a DOM element that has been removed.

3. The Execution Context & The Stack

Every time you run JavaScript, an Execution Context is created.

  • Global Execution Context: Created first. It creates the window object (in browsers) and the this keyword.
  • Function Execution Context: Created whenever a function is invoked.

The Phases

  1. Creation Phase:
    • Creates the Scope Chain.
    • Allocates memory for variables and functions (Hoisting).
    • Determines the value of this.
  2. Execution Phase: Code is run line by line.
var a = 'Hello';

function b() {
    console.log('Called b');
}

// Hoisting in action:
// a is 'undefined' during creation phase
// b is fully available in memory

4. Scope and the Temporal Dead Zone (TDZ)

Scope determines where variables are accessible.

  • Global Scope: Accessible everywhere.
  • Function Scope: Variables declared with var are limited to the function.
  • Block Scope: Variables declared with let and const are limited to the {} block.

The Temporal Dead Zone (TDZ)

Unlike var, which is hoisted as undefined, let and const are hoisted but not initialized. Accessing them before declaration results in a ReferenceError.

function tdzExample() {
    console.log(bar); // undefined (Hoisted)
    // console.log(foo); // ReferenceError (In TDZ)
    
    var bar = 1;
    let foo = 2;
}

5. The Holy Grail: Closures

A Closure is the combination of a function bundled together with references to its surrounding state (the lexical environment).

Why they matter:

  1. Data Encapsulation: Create private variables.
  2. State Persistence: Functions that “remember” data between executions.
function createCounter() {
    let count = 0; // Private state
    return {
        increment: function() {
            count++;
            return count;
        },
        decrement: function() {
            count--;
            return count;
        }
    };
}

const myCounter = createCounter();
console.log(myCounter.increment()); // 1
console.log(myCounter.increment()); // 2

6. Prototypes and Prototypal Inheritance

JavaScript does not use “classes” in the traditional sense. It uses Objects linking to other Objects.

The Prototype Chain

Every object has a hidden [[Prototype]] property (accessible via __proto__ or Object.getPrototypeOf). When you access a property, JS looks at the object. If not found, it looks at the prototype, then the prototype’s prototype, until it reaches null.

const animal = { eats: true };
const rabbit = Object.create(animal);
rabbit.jumps = true;

console.log(rabbit.eats); // true (found via prototype chain)

Function Prototypes vs. Object Prototypes:

  • Object.prototype is where properties live.
  • Function.prototype is where methods like call, apply, and bind live.

7. Mastering this & Context Binding

The value of this is not determined by where a function is defined, but how it is called.

  1. Implicit Binding: obj.method() -> this is obj.
  2. Explicit Binding: func.call(obj, arg1), func.apply(obj, [args]), or func.bind(obj).
  3. New Binding: new MyFunc() -> this is the new object.
  4. Default Binding: In non-strict mode, this is window. In strict mode, undefined.
  5. Lexical this (Arrow Functions): Arrow functions do not have their own this. They inherit it from the parent scope.

8. The Event Loop: Asynchronous Mastery

JavaScript is single-threaded, but it behaves as if it’s multi-threaded thanks to the Event Loop.

The Hierarchy of Execution

  1. Call Stack: Synchronous execution.
  2. Microtask Queue: Promises, process.nextTick (Node), queueMicrotask. (High Priority).
  3. Macrotask Queue: setTimeout, setInterval, setImmediate, I/O, UI Rendering.
console.log('1');
setTimeout(() => console.log('2'), 0);
Promise.resolve().then(() => console.log('3'));
console.log('4');
// Result: 1, 4, 3, 2 (3 comes before 2 because Microtasks run before the next Macrotask)

9. Advanced Patterns: Proxy, Reflect, and Symbols

Proxies

Allow you to intercept operations like property access or function calls.

const validator = {
    set: function(obj, prop, value) {
        if (prop === 'age' && value > 100) {
            throw new Error('Invalid age');
        }
        obj[prop] = value;
        return true;
    }
};
const person = new Proxy({}, validator);

Symbols

Symbols are unique and immutable primitives. They are used to create “hidden” properties that won’t show up in Object.keys() or for...in loops.

const HIDDEN = Symbol('hidden');
let obj = { [HIDDEN]: 'Secret' };

10. The Weird Parts: Quirks & Coercion

Abstract Equality (==) vs. Strict Equality (===)

Loose equality (==) follows the Abstract Equality Comparison Algorithm. It tries to convert types to match.

  • [] == ![] is true.
  • NaN === NaN is false.
  • 0 == "0" is true.
  • null == undefined is true.

Truthy and Falsy

Falsy values: false, 0, -0, 0n, "", null, undefined, NaN. Everything else is Truthy, including empty objects {} and empty arrays [].

11. Functional Programming: Monads and Composition

Mastery requires moving beyond loops into declarations.

Currying

Transforming a function with multiple arguments into a sequence of functions with one argument.

const multiply = a => b => a * b;
const double = multiply(2);
console.log(double(5)); // 10

Composition

Combining multiple functions to create a new one. f(g(x)).

12. Modules: ESM vs. CJS

  • CommonJS (CJS): require() and module.exports. Synchronous. Used in Node.js.
  • ES Modules (ESM): import and export. Asynchronous and static. The browser standard.

Mastery Tip: ESM imports are live bindings. If the exported value changes in the module, the importer sees the change.

13. High-Performance JavaScript: Web Workers

To avoid blocking the main thread (and keeping the UI at 60fps), use Web Workers. They run in a separate background thread with no access to the DOM.

// main.js
const worker = new Worker('worker.js');
worker.postMessage('Start calculation');

// worker.js
self.onmessage = function(e) {
    const result = heavyCalculation();
    self.postMessage(result);
};

14. Reflection and Metadata

The Reflect API provides methods for interceptable operations. It’s often used alongside Proxies to ensure the default behavior still occurs.

Reflect.has(obj, 'name'); // Same as 'name' in obj
Reflect.ownKeys(obj); // Returns all keys, including non-enumerable and symbols

Summary Checklist for JS Mastery

  1. V8 Optimization: Do you know how to write “monomorphic” code?
  2. Memory: Can you identify a memory leak using a Heap Snapshot?
  3. Context: Do you know exactly what this refers to in any callback?
  4. Async: Can you explain why await inside a loop can be a performance bottleneck?
  5. Architecture: Do you use composition over inheritance?

JavaScript is a living organism. New features like Record & Tuple, Temporal API, and Pipeline Operators are constantly shifting the landscape. Mastery is the pursuit of the “why” behind the “how.”