[2-4] 执行上下文与闭包 · 闭包 (Closures)

5 阅读3分钟

所属板块:2. 执行上下文与闭包(JS 的核心引擎)

记录日期:2026-03-xx
更新:遇到闭包相关应用或内存问题时补充

1. 闭包的严格定义

闭包(Closure)就是一个函数和它周围状态(词法环境)的引用捆绑在一起的组合。
通俗说:一个函数即使在它定义的环境已经销毁后,依然能访问那个环境里的变量

更精确的描述(来自 ECMAScript 规范):

一个函数和对其周围词法环境(Lexical Environment)的引用组合在一起,就形成了闭包。

核心特点:函数被当做返回值或参数传递到外部后,依然“记住”了诞生时的作用域链

2. 闭包的底层原理(连接 [2-1][2-2][2-3])

当外部函数执行完毕、其执行上下文按 [2-1] 所说正常出栈时:

  • 正常情况下,变量对象(VO)会被垃圾回收(第一板块记忆机制)
  • 但如果内部函数被返回到外部(形成了闭包),JS 引擎会把外部函数的变量对象“劫持”到堆内存,让它继续存活

这就是为什么闭包能“记住”外部变量:

  • [2-2] 的作用域链([[outer]] 指针)被保留
  • [2-3] 的 this(如果是普通函数)也随之保留

示例(最简闭包):

function outer() {
  let count = 0;
  return function inner() {
    count++;
    console.log(count);
  };
}

const fn = outer();   // outer 执行完,上下文本应销毁
fn();                 // 1
fn();                 // 2(count 依然存在)

3. 闭包的经典应用场景

  1. 数据私有化(封装)

    function createCounter() {
      let count = 0;          // 外部无法直接访问
      return {
        increment() { count++; return count; },
        get() { return count; }
      };
    }
    const counter = createCounter();
    
  2. 防抖与节流(最常用手写题)

    function debounce(fn, delay) {
      let timer = null;
      return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => fn(...args), delay);
      };
    }
    
  3. 函数柯里化(Currying)

    function curry(fn) {
      return function curried(...args) {
        if (args.length >= fn.length) return fn(...args);
        return (...next) => curried(...args, ...next);
      };
    }
    
  4. 缓存 / 记忆化(Memoization)
    用闭包保存计算结果,避免重复计算。

4. 闭包的副作用:内存泄漏风险

闭包会让外部函数的变量对象无法被 GC 回收。如果闭包长期存活 + 引用了大对象,就容易泄漏。

常见场景:

  • 全局变量保存了闭包
  • 事件监听器 / 定时器里的闭包没及时清理

解决办法:

let fn = outer();   // 使用完后
fn = null;          // 手动断开引用,让 GC 回收

5. 经典高频考题:for + setTimeout + 闭包

for (var i = 0; i < 5; i++) {
  setTimeout(() => console.log(i), 0);   // 输出 5 5 5 5 5
}

原因:var i 是函数/全局作用域,所有回调共用同一个 i([2-2] 作用域链)

两种解决方式:

  • 用 let(块级作用域,每轮循环都是新闭包)
  • 用 IIFE(立即执行函数创建独立作用域)
    for (var i = 0; i < 5; i++) {
      (function(j) {
        setTimeout(() => console.log(j), 0);
      })(i);
    }
    

6. 小结 & 第二板块完结

  • 闭包 = [2-1] 执行上下文 + [2-2] 作用域链 + [2-3] this 的“存活版”
  • 用得好是神器(私有化、柯里化、防抖);用不好就是内存泄漏
  • 遇到“变量值不对”“内存持续上涨”时,先检查是否有不必要的闭包

第二板块(执行上下文与闭包)到此全部结束。
这是 JS 引擎最核心的“内功心法”,掌握后看框架源码会顺畅很多。

返回总目录:戳这里