No.02
什么是闭包?有哪些应用场景和坑?
一句话回答
函数在定义时会记住它所在的词法作用域。即使这个函数在作用域之外执行,也能访问那些外部变量,"函数 + 它引用的外部变量"就是闭包。常用来封装私有变量、实现函数工厂和防抖节流;要注意循环中的变量捕获,以及闭包长期持有大对象导致内存无法回收。
详细解析
原理
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 面试官对练,面试记录也会保存下来。登录
这道题你掌握了吗?
选一个最接近的状态,没掌握的题会出现在"我的进度 · 待复习"里。
学习记录暂存在本机浏览器。登录后自动同步到账号,换设备也能看到。