Full-stack Web Technologies

CHAPTER 4
Closures

The scope of a variable

The scope of a variable is the pair of braces that contain it, and therefore the piece of code where its lifetime is defined.

{
  let x = 1; // 'x' is born
  x++;
  console.log(x);
} // 'x' dies

let x = 2; // another 'x' (global)
// ...

Outer variables

A function can access variables in outer scopes:

let x = "I am x";

function f() {
  console.log(`X says "${x}"`);
}

Nested functions

Since functions are values, they can be defined anywhere, including inside another function:

function showTime(hour: number, min: number, sec: number) {
  function _DD(x: number) {
    return String(x).padStart(2, '0');
  }
  console.log(`${_DD(hour)}:${_DD(min)}:${_DD(sec)}`);
}

The lexical scope

Any function can access variables from the outer scopes, lexically. This means that the accessible variables are determined at the time of definition, not at the time of execution.

/* 0 */
let a = false;
function f() { 
  /* 1 */
  let b = 1, c = "hi", d;
  const g = (e) => {
    /* 2 */
    return () => {
      /* 3 */
      return e ? a : b;
    }
  }
}

This example shows many nested scopes, numbered from 0 to 3, including parameters, local and global variables and 3 levels of inner functions:

The stack

At the time of execution, functions which are defined in diferent places (lexically) call each other and they place frames on the stack. At any one moment, on the stack, the different variables from all scopes are stored together, but a function can't access them if they come from different lexical scopes.

Closures

Closures are functions that outlive (typically are returned) the variables from outer scopes that they reference. Javascript detects this and forms a special object called a closure which stores the function and the values it needs attached to it.

const makeCounter = (initial: number = 0) => {
  let count: number = initial;
  return () => {
    const result = count;
    count++;
    return result;
  };
}

const c1 = makeCounter();
const c2 = makeCounter(10);
c1();

In this example, what makeCounter returns is a closure. It is a function that needs to use count, which is in its lexical scope, but count is a local variable of makeCounter and by the time we call the closure, makeCounter has finished (and count with it). So Javascript creates a function with its count attached. Every time we call makeCounter a new closure is made, with its own counter.