在 JavaScript 中,闭包(Closure) 是一个非常重要的核心概念,它允许函数「记住」并「访问」自己定义时的词法作用域(Lexical Scope),即使这个函数在其原始作用域之外执行。闭包的本质是函数与其引用的外部变量的捆绑关系。
🔍 闭包的核心特征
-
突破作用域链
函数可以访问定义时所在作用域的变量,即使外层函数已执行完毕。 -
持久化变量
被闭包引用的变量不会被垃圾回收机制回收,除非闭包本身被销毁。
🛠️ 闭包的形成条件
function outer() {
const outerVar = "我在外层!"; // 外层变量
function inner() { // 内层函数
console.log(outerVar); // 引用外层变量
}
return inner; // 返回内层函数
}
const closure = outer(); // outer 已执行完毕
closure(); // 输出:"我在外层!" ✅
- 嵌套函数:存在函数嵌套(如
inner嵌套在outer中) - 引用外部变量:内层函数引用了外层函数的变量(
outerVar) - 外部暴露:内层函数被导出到外层作用域(通过
return或赋值给全局变量)
🌟 闭包的经典用途
1. 封装私有变量(模块模式)
function createCounter() {
let count = 0; // 私有变量
return {
increment: () => ++count,
getCount: () => count
};
}
const counter = createCounter();
counter.increment(); // 1
counter.getCount(); // 1
- 外部无法直接修改
count,只能通过暴露的方法操作。
2. 保存状态(如事件回调)
function setupButton() {
const button = document.querySelector("#myBtn");
let clickCount = 0;
button.addEventListener("click", () => {
clickCount++;
console.log(`点击了 ${clickCount} 次`);
});
}
- 事件回调函数通过闭包记住了
clickCount的状态。
3. 函数工厂
function createMultiplier(factor) {
return function(x) {
return x * factor;
};
}
const double = createMultiplier(2);
console.log(double(5)); // 10 ✅
⚠️ 闭包的注意事项
-
内存泄漏风险
被闭包引用的变量不会被回收,需手动解除引用:function leak() { const bigData = new Array(1000000).fill("🚀"); return () => bigData; // 持续引用大数据 } const holder = leak(); // 即使不再使用,bigData 仍存在内存中 -
循环中的闭包陷阱
for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 100); // 输出 3 3 3 ❌ }解决方案:使用
let或 IIFE(立即执行函数)创建新作用域:for (let i = 0; i < 3; i++) { // let 创建块级作用域 setTimeout(() => console.log(i), 100); // 0 1 2 ✅ }
🌐 闭包的底层原理
JavaScript 引擎通过「作用域链(Scope Chain)」实现闭包:
- 函数在定义时,会保存其所在作用域的引用([[Environment]] 属性)。
- 当函数执行时,即使外层函数已销毁,其作用域仍被内层函数引用,因此不会被垃圾回收。
💡 一句话总结
闭包 = 函数 + 它捕获的外部变量。它是 JavaScript 实现模块化、状态保持和高级编程模式的基石。