JavaScript Hoisting and Scope

Understanding variable hoisting and scope rules in JavaScript

By Hank Kim

JavaScript Hoisting and Scope

Understanding variable hoisting and scope rules in JavaScript

JavaScript Hoisting and Scope

1. Hoisting

Definition

  • Hoisting refers to the process by which variable and function declarations are moved to the top of their scope during the compilation (parsing) phase.
  • In practice, the code does not physically move; instead, the JavaScript engine registers declarations in memory before executing code.
  • This allows variables and functions to be accessed before their declaration appears in the source code.

Variable Hoisting

  • Variables declared with var:

    • Declarations are hoisted, but initializations are not.
    • Accessing a var variable before initialization yields undefined.
    console.log(x); // undefined
    var x = 10;
    
  • Variables declared with let and const:

    • Also hoisted, but placed in a Temporal Dead Zone (TDZ) until their declaration is executed.
    • Accessing them before initialization throws a ReferenceError.
    console.log(y); // ReferenceError
    let y = 20;
    

Function Hoisting

  • Functions declared with the function keyword are hoisted with both declaration and initialization.
  • They can be called before their definition appears in the code.

    greet(); // "Hello"
    function greet() {
      console.log("Hello");
    }
    
  • Internally, function declarations are fully initialized during hoisting, unlike variables which undergo separate declaration and initialization phases.

Why TDZ Exists

  • ES6 introduced let and const with TDZ (Temporal Dead Zone) to improve code predictability.
  • Prevents accidental access of uninitialized variables.
  • Increases safety by avoiding subtle bugs caused by hoisting.

Hoisting in the Execution Model

  • JavaScript is an interpreted language, but engines perform a parsing/compilation step before execution:

    1. Parsing Phase: Builds the AST (Abstract Syntax Tree) and creates the execution context. Declarations are registered in memory.
    2. Execution Phase: Code runs line by line. Variable initialization and function calls are executed.
  • Hoisting occurs during the parsing phase, which explains why declarations appear to “move up” in scope.


2. Scope

Definition

  • Scope is the region of code where a variable or function is accessible.
  • Determines the lifetime and visibility of variables.
  • Accessing a variable outside its scope leads to a ReferenceError.

Types of Scope

  1. Global Scope

    • Variables declared outside any function/block are globally accessible.
    • Example:

      var globalVar = "I am global";
      
  2. Local Scope

    • Created inside functions or blocks.
    • Function Scope: Variables declared with var are scoped to the function.
    • Block Scope: Variables declared with let and const are scoped to the nearest {} block.

Lexical (Static) Scope

  • JavaScript uses lexical scoping: scope is determined by where the code is written, not by where it is executed.
  • Nested functions can access variables from their outer functions, but the reverse is not possible.

    function outer() {
      let a = 10;
      function inner() {
        console.log(a); // 10
      }
      inner();
    }
    outer();
    

How Variable Resolution Works

  • Every function invocation creates a new Execution Context.
  • Each execution context has a Lexical Environment:

    • Environment Record: Stores local variable/function declarations.
    • Outer Reference: Points to the parent scope’s lexical environment.
  • When a variable is referenced:

    1. The engine checks the current environment record.
    2. If not found, it follows the outer reference chain.
    3. If still not found, it checks the global environment.
    4. If missing, a ReferenceError is thrown.

3. Hoisting + Scope Together

  • Hoisting determines when variables/functions are registered in memory.
  • Scope determines where they are accessible.
  • Combined behavior:

    • Variables with var are hoisted to their enclosing function/global scope and initialized with undefined.
    • let/const are hoisted but locked in the TDZ until initialization is reached in code.
    • Function declarations are hoisted with full initialization, available throughout their scope.
  • This explains why:

    console.log(a); // undefined (var hoisting)
    var a = 1;
    
    console.log(b); // ReferenceError (TDZ)
    let b = 2;
    
    greet(); // Works (function hoisting)
    function greet() {
      console.log("hi");
    }
    

4. Summary

  • Hoisting: Declarations are processed during the parsing phase, before execution.
  • Variables:

    • var → hoisted, initialized to undefined.
    • let/const → hoisted, but not initialized (TDZ applies).
  • Functions: fully hoisted (declaration + initialization).
  • Scope: Defines the visibility of variables (global, function, block).
  • Lexical Scope: Variable scope is determined by the code’s written structure, not runtime.
  • Execution Context: Each function creates a lexical environment with an environment record and an outer reference.

Tags: JavaScript