Yuan's Blog
EN

Variables, Scope, and Closures

What are the differences between var, let, and const?

  1. Scope: var can be global or function-scoped; let and const are block-scoped.

  2. Declaration: var can be redeclared; let and const cannot be redeclared.

  3. Hoisting: Variables declared with var are hoisted and automatically initialized to undefined, so using them before declaration does not throw an error (you simply get undefined). Variables declared with let or const are hoisted but not initialized; they enter the Temporal Dead Zone (TDZ). Using them before their declaration results in an error.

  4. Reassignment: In most aspects let and const behave the same. The major difference is that variables declared with let can be reassigned, while those declared with const cannot.

What is Hoisting?

  • Hoisting: the concept that, during JavaScript’s compilation phase, variable and function declarations are stored in memory before code execution.
  • var hoisting: when accessed early, the value is undefined.
  • let / const: when hoisted into block scope, they are not initialized. This state is called uninitialized, often described as being in the Temporal Dead Zone (TDZ).
  • Function declarations:
    • A function assigned to a variable declared with var: before the declaration, the variable’s value is undefined, so calling it results in an error.
    • A function assigned to a variable declared with let: before the declaration, the variable is in the TDZ, so calling it results in an error.

foo(); // 1
function foo() {
  console.log(1);
}

foo(); // Uncaught TypeError: foo is not a function
var foo = function () {};

foo(); // Uncaught ReferenceError: foo is not defined
let foo = function () {};

What are JavaScript Scope and the Scope Chain?

  • Scope refers to the current execution context—the environment the program is currently in. Within this context, we can easily access values and expressions.

    • Global Scope
    • Block Scope: only variables defined with let and const belong to block scope; variables defined with var do not—they are function-scoped instead.
    • Function Scope: scope created by a function.
  • What is the Scope Chain? When JavaScript accesses a variable, it first looks in the current scope. If the variable is not found, it continues searching outward through parent scopes. This process continues until the global scope is reached. If the variable is still not found, an error is thrown. This layered lookup structure is called the scope chain.

What is a Closure?

  • A closure is when an inner function can access variables from an outer function and also remembers those variables. Because it can remember external variables, closures are often used for state preservation.

  • Closure Use Case 1 — State Preservation

function useState(initialState) {
  let state = initialState;

  function getState() {
    return state;
  }

  function setState(updatedState) {
    state = updatedState;
  }
  return [getState, setState];
}

const [count, setCount] = useState(0);

count(); // 0
setCount(1);
count(); // 1
setCount(500);
count(); // 500
  • Closure Use Case 2 — Caching
function memoize(fn) {
  // Declare a cache object to store cached results.
  // Because of the closure, the returned function below
  // can access this cache variable.
  const cache = {};

  // Use the spread operator to capture arguments.
  return (...args) => {
    // Use the arguments as the cache key.
    const key = JSON.stringify(args);

    // If the key exists in the cache, return the cached value.
    if (key in cache) {
      return cache[key];
    } else {
      // Otherwise, compute the result with the received arguments.
      const val = fn(...args);
      // Store the result in the cache for next time.
      cache[key] = val;
      return val;
    }
  };
}

- Closure Use Case 3  Simulating variables that cannot be directly accessed from the outside.
```JavaScript
var counter = (function () {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function () {
      changeBy(1);
    },
    decrement: function () {
      changeBy(-1);
    },
    value: function () {
      return privateCounter;
    },
  };
})();

console.log(counter.value()); // logs 0
counter.increment();
counter.increment();
console.log(counter.value()); // logs 2
counter.decrement();
console.log(counter.value()); // logs 1