主题
闭包
闭包可以先理解成:函数在创建时,记住了自己所在的词法作用域,因此即使函数在外部执行,仍然能访问当时作用域里的变量。
闭包是怎么形成的
最常见的情况是:内部函数用到了外层函数的变量。
js
function createCounter() {
let count = 0
return function () {
count++
return count
}
}这里返回的函数在外部执行时,仍然可以访问 count,这就是闭包。
不要把闭包理解成“特殊语法”
闭包不是单独的语法点,而是函数和词法作用域一起工作的自然结果。
只要出现下面这类情况,就很容易形成闭包:
- 函数返回另一个函数
- 函数作为参数传出去后延后执行
- 内部函数引用了外层变量
一个典型例子
js
function createAdder(base) {
return function (value) {
return base + value
}
}
const add10 = createAdder(10)
console.log(add10(5)) // 15add10 执行时,createAdder 早已运行结束,但 base 仍然可用。
为什么外层函数结束了,变量还在
因为只要返回出去的函数还在被引用,相关的词法环境就还不能被回收。
js
function createHandler() {
const data = [1, 2, 3]
return function () {
console.log(data.length)
}
}
const handler = createHandler()这里 data 是否能被回收,取决于 handler 是否还活着。
闭包最常见的使用场景
封装私有状态
js
function createCounter() {
let count = 0
return {
increment() {
count++
return count
},
getCount() {
return count
},
}
}工厂函数
js
function createLogger(prefix) {
return function (message) {
console.log(`[${prefix}] ${message}`)
}
}回调延后执行
js
function setup() {
const name = 'Lee'
setTimeout(() => {
console.log(name)
}, 1000)
}闭包和内存泄漏不是一回事
这是高频误区。
闭包本身不是内存泄漏。它只是让某些变量因为“后面还可能用到”而继续存活。
真正的问题是:
- 数据其实已经不需要了
- 但外部引用没有释放
- 导致本该回收的内容一直留着
什么情况下要警惕
更需要注意的是这类情况:
- 闭包里持有大对象
- 闭包被长期挂在全局变量、定时器、事件监听器上
- 本来不再需要,但引用一直没清理
js
function bind() {
const largeData = new Array(1000000).fill(0)
return function () {
return largeData.length
}
}
const handler = bind()如果 handler 一直存在,largeData 就不会被释放。
怎么判断是不是闭包导致的问题
可以先顺着这个思路判断:
- 内部函数是否引用了外层变量
- 这个函数是否还被外部持有
- 外层变量是否其实已经不再需要
如果前三步都成立,就要考虑是否存在不必要的长期引用。
使用建议
- 先把闭包理解成“函数记住外层作用域”,不要把它神秘化。
- 需要私有状态、延迟执行、工厂函数时,闭包很自然。
- 闭包里不要无意义持有大对象或 DOM 引用。
- 排查内存问题时,重点看“引用是否还存在”,而不是只看“有没有闭包”。
