Lesson 40-You Don’t Know JavaScript

Scope and Closures

What is Scope?

Compilation Principles

JavaScript is a compiled language, but its compilation process differs from traditional compiled languages. Instead of compiling the entire code before execution, JavaScript engines perform Just-In-Time (JIT) Compilation during execution.

The compilation process consists of three stages:

  1. Lexical Analysis: Breaks the code into individual lexical units (tokens), such as variable names, keywords, operators, etc.
  2. Syntax Analysis: Combines tokens into an Abstract Syntax Tree (AST), representing the code’s syntactic structure.
  3. Code Generation: Converts the AST into executable machine code.

Understanding Scope

Scope refers to the region in a program where variables are defined, determining their visibility and lifecycle. JavaScript has two types of scope: global scope and local scope.

Global Scope

Global scope is accessible from anywhere in the code. In a browser environment, the global scope is the window object; in Node.js, it is the global object.

var globalVar = 'I am global';

function foo() {
    console.log(globalVar); // Can access global variable
}

foo(); // Output: I am global
Local Scope

Local scope is defined within a function and is only accessible inside that function.

function bar() {
    var localVar = 'I am local';
    console.log(localVar); // Can access local variable
}

bar(); // Output: I am local
console.log(localVar); // Error: localVar is not defined

Lexical Scope

Lexical scope is determined during the code’s writing phase, based on the code structure, not the function’s call site.

function outer() {
    var outerVar = 'I am outer';

    function inner() {
        console.log(outerVar); // Can access outer function's variable
    }

    inner();
}

outer(); // Output: I am outer

In this example, the inner function can access the outerVar variable from the outer function because its lexical scope includes the outer function’s scope.

Lexical Scope

Lexical Phase

Lexical scope is established during the lexical analysis phase, based on the code’s structure, not the function’s call site.

function foo() {
    var a = 1;

    function bar() {
        var b = 2;

        function baz() {
            console.log(a, b); // Can access outer functions' variables
        }

        baz();
    }

    bar();
}

foo(); // Output: 1 2

In this example, the baz function can access variables from foo and bar because its lexical scope includes their scopes.

Deceiving Lexical Scope

Although lexical scope is determined during lexical analysis, certain techniques can be used to “deceive” it, allowing functions to access variables they otherwise couldn’t.

eval

The eval function executes a string as code, altering the lexical scope.

function foo(str) {
    eval(str); // Execute the input string
    console.log(a); // Can access variable defined in eval
}

foo('var a = 1'); // Output: 1
with

The with statement adds an object to the top of the scope chain, making variable lookups start from that object.

var obj = { a: 1 };

function foo() {
    with (obj) {
        console.log(a); // Can access obj's variable
    }
}

foo(); // Output: 1

Note: Both eval and with negatively impact performance and maintainability and should be avoided.

Performance

Lexical scope lookups are determined during compilation, making them generally more efficient than dynamic scope. When resolving variables, the JavaScript engine starts from the current scope and moves up the scope chain until the variable is found or the global scope is reached.

Function Scope and Block Scope

Function Scope

In JavaScript, functions are first-class citizens, meaning they can be passed as arguments, returned as values, and defined within other functions. Variables defined inside a function are only visible within that function, creating function scope.

function foo() {
    var a = 1;

    function bar() {
        var b = 2;
        console.log(a + b); // Can access outer function's variable
    }

    bar();
}

foo(); // Output: 3

In this example, the bar function can access the a variable from foo because its lexical scope includes foo’s scope.

Hiding Internal Implementation

Function scope can be used to hide internal implementation details, preventing external code from accessing or modifying internal variables.

function createCounter() {
    var count = 0;

    return function() {
        count++;
        return count;
    };
}

var counter = createCounter();
console.log(counter()); // Output: 1
console.log(counter()); // Output: 2

In this example, createCounter returns a closure that encapsulates the count variable. External code cannot directly access or modify count, only interacting with it through the closure’s interface.

Function Scope

Function scope refers to the scope defined within a function, accessible only inside that function.

function foo() {
    var a = 1;
    console.log(a); // Can access local variable
}

foo(); // Output: 1
console.log(a); // Error: a is not defined

In this example, the a variable is only visible within the foo function, inaccessible to external code.

Block Scope

Block scope refers to the scope defined within code blocks (e.g., if, for, while), accessible only inside that block.

if (true) {
    let b = 2;
    console.log(b); // Can access block-scoped variable
}

console.log(b); // Error: b is not defined

In this example, the b variable is only visible within the if block, inaccessible to external code.

Garbage Collection

JavaScript’s garbage collector automatically reclaims memory no longer in use. When a variable is no longer referenced, its memory is freed.

function foo() {
    var a = 1;
    return function() {
        console.log(a);
    };
}

var bar = foo();
bar(); // Output: 1

// After foo executes, a is no longer referenced and can be garbage collected

In this example, after foo executes, the a variable is no longer referenced (except by the closure), and the garbage collector can free its memory.

Hoisting

Chicken or Egg?

In JavaScript, variable and function declarations are hoisted to the top of their scope, but variable assignments are not.

console.log(a); // Output: undefined
var a = 1;

In this example, var a = 1; is hoisted as var a;, so console.log(a); outputs undefined.

Compiler Strikes Again

The JavaScript engine compiles code before execution, hoisting variable and function declarations during this process.

foo(); // Output: I am foo

function foo() {
    console.log('I am foo');
}

In this example, the foo function declaration is hoisted to the top of the scope, allowing foo(); to execute correctly.

Functions First

Function declarations are hoisted before variable declarations.

var foo = 1;

function foo() {
    console.log('I am foo');
}

console.log(foo); // Output: 1

In this example, the function declaration foo is hoisted first, but the variable assignment foo = 1 overwrites it, so console.log(foo); outputs 1.

Scope Closures

The Essence

A closure is a function that retains access to variables from its enclosing scope, even after the outer function has finished executing.

function createCounter() {
    let count = 0;

    return function() {
        count++;
        return count;
    };
}

let counter = createCounter();
console.log(counter()); // Output: 1
console.log(counter()); // Output: 2

In this example, createCounter returns a closure that retains access to the count variable. Even after createCounter finishes, the closure can still access and modify count.

Now I Get It

Closures work through the scope chain. When a function is defined inside another, its scope chain includes the outer function’s scope. After the outer function executes, the inner function retains a reference to that scope chain, allowing access to its variables.

Closures have many practical uses, including:

  1. Data Hiding: Encapsulating internal state to prevent external access or modification.
  2. Callbacks: Creating callback functions that retain access to outer variables.
  3. Modularity: Building modules where variables and functions are private to the module.

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here

Share your love