什么是闭包?有哪些应用场景和坑?

进阶高频原理约 3 分钟读完

一句话回答

函数在定义时会记住它所在的词法作用域。即使这个函数在作用域之外执行,也能访问那些外部变量,"函数 + 它引用的外部变量"就是闭包。常用来封装私有变量、实现函数工厂和防抖节流;要注意循环中的变量捕获,以及闭包长期持有大对象导致内存无法回收。

详细解析

原理

JS 采用词法作用域:函数能访问哪些变量,在定义时就确定了,和在哪里调用无关。外层函数执行结束后,只要内部函数还被引用,它用到的外层变量就不会被垃圾回收。

JavaScript
function createCounter() {
  let count = 0
  return {
    inc: () => ++count,
    get: () => count,
  }
}

const counter = createCounter()
counter.inc()
counter.inc()
console.log(counter.get()) // 2,外部无法直接修改 count

常见应用

  • 私有变量、模块化:如上面的计数器,只暴露必要的方法
  • 函数工厂与柯里化:const add = (a) => (b) => a + b
  • 防抖节流:用闭包保存定时器
JavaScript
function debounce(fn, delay) {
  let timer = null
  return function (...args) {
    clearTimeout(timer)
    timer = setTimeout(() => fn.apply(this, args), delay)
  }
}
  • React Hooks:useState、useEffect 都依赖闭包,"过期闭包"问题也来源于此

经典坑:循环中的闭包

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

var 是函数作用域,三个回调引用的是同一个 i,执行时循环早已结束。改成 let 后,每次迭代都会创建一个新的 i 绑定,输出 0 1 2。

面试官可能追问

闭包会导致内存泄漏吗?

闭包本身不是泄漏,它只是延长了变量的生命周期。问题出在不再需要的闭包仍被引用,比如没有移除的事件监听、没清除的定时器、全局缓存里的回调,导致它引用的大对象一直无法回收。用完要及时解绑、清除或置空。

React 里的"过期闭包"是什么?怎么解决?

组件每次渲染都会创建新的函数,旧的回调(比如 setInterval 里的)捕获的还是旧渲染时的 state,所以读到的值不更新。解决办法:用函数式更新 setCount((c) => c + 1);把依赖写进 useEffect 的依赖数组;或者用 useRef 保存最新值。

V8 中闭包会保留外层的所有变量吗?

不会。V8 会分析内部函数实际引用了哪些外部变量,只把这些变量放到堆上的 Context 对象中。但同一个作用域里的多个闭包共享同一个 Context:只要其中一个闭包还活着,其他闭包引用的变量也不会被回收。

易错点

  • 闭包捕获的是变量本身,不是创建时的值快照
  • let 能解决循环问题,是因为每次迭代都有新的绑定,不只是因为它有块级作用域

AI 模拟面试官

用自己的话回答,AI 对照参考答案打分、指出遗漏,再追问,最多 3 轮

登录后就可以和 AI 面试官对练,面试记录也会保存下来。登录

这道题你掌握了吗?

选一个最接近的状态,没掌握的题会出现在"我的进度 · 待复习"里。

学习记录暂存在本机浏览器。登录后自动同步到账号,换设备也能看到。