JavaScript 闭包:词法环境机制与实际应用
JavaScript 中的闭包是一个函数与其词法环境的组合,其中内部函数在外部函数执行完成后仍能访问外部作用域的变量。这使得在异步上下文中实现状态封装和可预测行为成为可能。
这一概念源于阿隆佐·丘奇的 lambda 演算。高阶函数返回或接受其他函数,捕获自由变量——即内部使用但外部声明的变量。
一个基本的计数器示例展示了其原理:
function counter() {
let count = 0
return function(step) {
count = count + step
return count
}
}
const myCounter = counter()
console.log(myCounter(1)) // 1
console.log(myCounter(1)) // 2
console.log(myCounter(1)) // 3
内部函数闭包了 count,防止其被垃圾回收。这是一个实时引用:更改会实时反映。
闭包作为面向对象编程的类比
闭包模拟了对象的私有字段和公共 API。一个带有方法的计数器:
function Counter(initialValue = 0) {
let count = initialValue
return {
increment: function(step = 1) {
return count += step
},
decrement: function(step = 1) {
return count -= step
},
getValue: function() {
return count
},
reset: function() {
return count = initialValue
}
}
}
const myCounter = Counter(10)
console.log(myCounter.getValue()) // 10
console.log(myCounter.increment(5)) // 15
使用带有私有字段(#count)的类实现等价逻辑。在 Pinia/Vuex 存储中,闭包封装了响应式状态:
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const initValue = 0
function increment(step) {
count.value += step
}
return { count, increment }
})
循环和迭代器中的细微差别
闭包在循环中的行为取决于变量提升和块作用域。
使用 var——所有迭代共享一个变量:
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i) // 3, 3, 3
}, 100)
}
使用 let——每次迭代创建一个独立的词法环境:
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i) // 0, 1, 2
}, 100)
}
迭代器如 forEach、map、filter 自动创建闭包:
[0, 1, 2].forEach(function(i) {
setTimeout(function() {
console.log(i) // 0, 1, 2
}, 100)
})
实践:回调和事件
在异步代码中,闭包解决了上下文问题。
带有唯一 ID 的 Ajax 请求:
function ajaxRequest(url) {
let requestId = Math.random().toString(36).substring(2)
fetch(url).then(data => {
console.log(`请求 ${requestId} 完成:`, data)
}).catch(error => {
console.log(`请求 ${requestId} 失败:`, error.message)
})
}
事件处理程序:
function clickSetup(elemId, message) {
const elem = document.getElementById(elemId)
elem.addEventListener('click', function() {
console.log(message)
})
}
箭头函数与常规函数工作方式相同。
优化和调试
JS 引擎(如 V8)从闭包中排除未使用的变量以节省内存。在 DevTools(作用域 → 闭包)中,仅显示相关变量。
关键实践:
- 在循环中使用
let/const实现块作用域。 - 优先使用闭包而非类来实现私有状态。
- 在异步回调中,尽早固定上下文。
- 监控长生命周期闭包的内存使用。
关键要点
- 闭包 = 函数 + 词法环境,包含对变量的实时引用。
- 循环中的
let创建每次迭代的闭包,var则不会。 - 模拟面向对象编程的私有字段,用于 Vuex/Pinia 存储。
- 引擎优化:未使用的变量被排除。
- 对异步回调和事件至关重要。
— Editorial Team
暂无评论。