BRUCE_FEBRUCE_FE

EN/CH Mode

BRUCE_FE JS Interview Notes - JavaScript Closure

Understand JavaScript Closure and Lexical Scope from basics, learn how React remembers state with useState model, and master closure principles and pitfalls with memory leak cases and common interview questions.

影片縮圖

Lazy to read articles? Then watch videos!

What is Closure? Why does JavaScript need it?

A closure is a combination of a function and the lexical environment within which that function was declared. Even when the function is executed in a different scope, it can still access variables from its original scope.

  • Preserve intermediate state in functions (e.g., counters)
  • Encapsulate data, implement private variables
  • Provide memory (e.g., memoization)
  • Handle asynchronous flow and event callbacks
function createCounter() {
  let count = 0;
  return function () {
    count += 1;
    return count;
  };
}

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

What is Lexical Scope? How does it relate to Closure?

JavaScript uses lexical scoping, meaning 'functions can access variables from their definition environment, not their execution environment'.

Closure is an extension of this mechanism, allowing functions to remember variables from their definition time even in the future.

function outer() {
  const name = 'Bruce';
  function greet() {
    console.log('Hello, ' + name);
  }
  return greet;
}

const sayHello = outer();
sayHello(); // Hello, Bruce

The Truth About Memory Leaks: When Closures Become Landmines

Closures extend variable lifetimes. When they reference large data or DOM elements and are bound to long-lived structures, they can prevent memory from being released.

Example 1: Global Variables with DOM

let cache = {};

function bindElement(elementId) {
  const element = document.getElementById(elementId);
  cache[elementId] = () => {
    console.log(element.innerText);
  };
}

bindElement('myDiv');

DOM elements are referenced by closures and stored in global cache. Even if elements are removed, GC cannot reclaim them. Solution: Set cache[elementId] = null when no longer needed.

Example 2: setTimeout Long-term Variable Reference

function setupLogger() {
  let bigData = new Array(1000000).fill('*');
  setTimeout(() => {
    console.log(bigData[0]);
  }, 10000);
}

Timer callback references a large array. Even after function execution, bigData remains in memory for 10 seconds. The issue is that the closure keeps setTimeout referencing bigData, preventing garbage collection. Solution: Clear timer or keep only necessary data.

Why can React's useState remember values? Why do multiple Hook calls need closures?

React re-executes the entire component function on each render. But the getter/setter returned by useState are closures that can remember their corresponding state.

function simpleUseState(initialValue) {
  let value = initialValue;
  return [
    () => value,
    (newVal) => { value = newVal; }
  ];
}

const [getA, setA] = simpleUseState(1);
const [getB, setB] = simpleUseState(100);

setA(2);
console.log(getA()); // 2
console.log(getB()); // 100

🔥 Common Interview Questions

(1) What is closure? Why does JavaScript need it?

A closure is a combination of a function and its variable environment, allowing the function to remember variables from its definition time. JavaScript uses it to preserve state, encapsulate data, and handle callbacks.

function createCounter() {
  let count = 0;  // This variable is preserved by closure
  
  return function() {
    count += 1;
    return count;
  };
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

(2) What is lexical scope? How does it relate to closure?

Lexical scope means variable scope is determined by where it's defined, not where it's executed. Closures build on this feature, allowing functions to carry their 'birth environment' wherever they execute.

let name = 'Global';

function outer() {
  let name = 'Outer';
  
  function inner() {
    console.log(name); // Uses outer's name
  }
  
  return inner;
}

const innerFn = outer();
innerFn(); // 'Outer' - Even when executed in global scope, remembers its definition environment

(3) How to implement private variables using closure?

Closures can create private variables that can only be accessed through specified methods, implementing encapsulation similar to object-oriented concepts.

function createSecret() {
  let secret = '1234';
  return {
    get() { return secret; },
    set(val) { secret = val; }
  };
}

const user = createSecret();
console.log(user.get()); // '1234'
user.set('abcd');
console.log(user.get()); // 'abcd'
// console.log(secret); // Error! Cannot access directly from outside

(4) How does React's useState use closure to remember values? Why do multiple Hook calls need closure?

Each useState creates an independent closure, providing a dedicated 'safe' for each state, keeping states independent and non-interfering during re-renders.

function simpleUseState(initialValue) {
  let value = initialValue;
  return [
    () => value,
    (newVal) => { value = newVal; }
  ];
}

const [getA, setA] = simpleUseState(1);
const [getB, setB] = simpleUseState(100);

setA(2);
console.log(getA()); // 2
console.log(getB()); // 100

(5) What scenarios can cause memory leaks with closures?

When closures continuously reference large data or DOM elements without release, they prevent garbage collection, consuming resources like a forgotten running tap.

  • Global variables binding DOM (preventing elements from being GC'd)
  • setTimeout or event listeners binding variables without cleanup
// Memory leak example
function setupEventHandler() {
  const largeData = new Array(10000).fill('data');
  
  document.getElementById('button').addEventListener('click', function() {
    console.log(largeData.length); // Continuously references large data
  });
  
  // Correct approach: Save reference and remove at appropriate time
  // const handler = function() { console.log(largeData.length); };
  // document.getElementById('button').addEventListener('click', handler);
  // Later can use removeEventListener('click', handler) to remove
}

(6) What is the relationship between factory functions and closures?

Factory functions use closures to mass-produce functions with similar functionality but different parameters, each function remembering its own independent environment.

function createMultiplier(factor) {
  return function(num) {
    return num * factor;
  };
}

const double = createMultiplier(2);
const triple = createMultiplier(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15