Skip to content

闭包与作用域

闭包不是一种特殊语法,而是函数与其词法环境的组合体。理解了词法作用域,闭包就是顺理成章的副产物:函数记住了它在哪出生,而不是它在哪被调用。

词法作用域决定变量去哪里找

JavaScript 的作用域在编写时就确定了,由代码的物理位置决定。查找变量时沿作用域链向外逐层寻找,找到就停,找不到就报 ReferenceError

js
const name = '南终'

function greet() {
  // 这里看不到 inner 的 message
  return name // 往外层找到 '南终'
}

function outer() {
  const message = 'hi'
  function inner() {
    return message // 往外层找到 'hi'
  }
  return inner
}

var 会变量提升且不产生块级作用域;let / const 产生块级作用域,存在"暂时性死区"。新代码一律用 let / const

闭包本质:函数携带它的词法环境

当一个函数被拿到它定义的作用域之外执行,它依然能访问定义时的变量——这就是闭包。

js
function makeCounter() {
  let count = 0
  return {
    inc() { count += 1; return count },
    get() { return count }
  }
}

const counter = makeCounter()
counter.inc() // 1
counter.inc() // 2
counter.get() // 2

count 既不是全局变量,也无法从外部直接访问——它被闭包"私有化"了。这是 JavaScript 实现封装和数据隐藏的标准手段。

IIFE 与模块模式

在 ESM 普及之前,IIFE(立即执行函数)是隔离作用域、避免污染全局的主要方式。模块模式在它的基础上封装私有状态:

js
const userRepo = (function () {
  const users = [] // 私有

  return {
    add(u) { users.push(u) },
    size() { return users.length }
  }
})()

现代项目用 ESM 的 import / export 即可天然隔离作用域。IIFE 主要在写 SDK 入口、为老环境兼容时才需要。

循环里的闭包陷阱

var 没有块级作用域,循环中创建的闭包共享同一个变量,导致所有回调拿到的都是循环结束后的值:

js
for (var i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0)
}
// 输出 3 3 3,而不是 0 1 2

原因:三个箭头函数引用的是同一个 i,循环结束时它已经是 3。

解法:用 let(每轮迭代都是新的绑定),或者用 IIFE 当场拷贝当前值:

js
for (let i = 0; i < 3; i++) {
  setTimeout(() => console.log(i), 0) // 0 1 2
}

闭包与内存

闭包会让它引用的外层变量一直存活在内存里,只要闭包本身还可达。这既是特性也是风险:

情况后果处理
长生命周期对象持有大闭包内存不释放用完置 null,或缩小闭包引用范围
闭包内引用了 DOM 节点DOM 卸载后仍被引用,无法回收解绑事件、清除引用
定时器回调捕获大对象对象常驻在结束时 clearInterval 并释放

不是所有闭包都会泄漏。只有当闭包被长生命周期的东西(全局变量、未解绑的事件、未清理的定时器)持有时,内存才会迟迟不释放。

何时用闭包,何时别用

闭包适合:私有状态封装、工厂函数、回调中保留上下文、柯里化与函数式组合。

不适合:单纯为了"避免全局污染"包一层(用模块化代替)、把简单逻辑包装成多层闭包导致难调试、需要被序列化或跨上下文传递的函数(闭包无法被 JSON.stringify 带走)。

一个判断标准:如果外层变量除了被这个函数使用之外没有别的用途,那它就是闭包的合理用法;如果外层变量本来就该是普通模块状态,就别硬塞进闭包里。

检查表

  • 变量声明统一用 let / const,循环变量用 let 避免闭包共享。
  • 需要私有数据时优先用闭包或模块封装,而不是挂在全局或 this 上。
  • 长生命周期对象持有闭包时,确认有明确的释放路径(解绑、清理、置空)。
  • 事件回调和定时器在组件/页面销毁时统一解绑,避免闭包持有 DOM 或大对象。
  • 不要为了用闭包而把简单代码层层包裹;可读性优先于"看起来高级"。

为复用而记录,为理解而整理。