闭包是什么?符合哪些特征才算是闭包?请举例。
非常棒的问题 ⚡️这说明你已经在深入理解 JavaScript 的核心机制之一 —— 闭包(Closure)。
我们来从「定义 → 特征 → 原理 → 示例」四个方面深入讲解 👇
🧠 一、闭包是什么(定义)
闭包(Closure)是指:
一个函数可以访问其词法作用域(lexical scope)之外的变量的能力。
简单说: 👉 当内部函数引用了外部函数的变量,并且外部函数已经执行完毕,此时依然能访问这些变量,就形成了闭包。
🔍 二、闭包的三个必要条件(特征)
| 特征 | 说明 |
|---|---|
| 1️⃣ 嵌套函数 | 必须在一个函数内部再定义函数。 |
| 2️⃣ 引用了外部变量 | 内部函数必须使用了外部函数的变量。 |
| 3️⃣ 外部函数执行完但变量仍被引用 | 外部函数执行结束后,变量仍保存在内存中(未被 GC 回收)。 |
满足以上三个条件,就形成了闭包。
🧩 三、为什么闭包能存在(原理)
JS 的作用域是 词法作用域(lexical scope),由代码定义位置决定。 当一个函数被定义时,它会“记住”当时的作用域链(scope chain)。 即使函数在外部被调用,也能访问它定义时的变量环境。
这就是闭包的底层实现原理。
💡 四、举例说明
✅ 示例 1:最典型的闭包
js
function createCounter() {
let count = 0; // 外部变量
return function() {
count++; // 内部函数引用了外部变量
return count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3🔍 分析:
createCounter执行后,正常来说变量count应该被销毁;- 但内部函数仍在使用
count; - 因此 JS 引擎保留了
count的环境,这就是闭包。
✅ 示例 2:循环绑定常见问题(闭包的应用)
js
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 1000);
}
// 输出:3 3 3原因:var 没有块级作用域,所有回调共享一个 i。
✅ 用闭包修复:
js
for (var i = 0; i < 3; i++) {
((j) => {
setTimeout(() => console.log(j), 1000);
})(i);
}
// 输出:0 1 2这里 (j => {...})(i) 创建了闭包,把每次的 i 值保存起来。
✅ 示例 3:模块化应用
js
function createModule() {
let secret = 'my-secret';
return {
getSecret() {
return secret;
},
setSecret(newVal) {
secret = newVal;
}
};
}
const module = createModule();
console.log(module.getSecret()); // 'my-secret'
module.setSecret('updated');
console.log(module.getSecret()); // 'updated'这是闭包在封装私有变量时的常见用法。
🧩 五、闭包的注意点(易错与优化)
| 问题 | 原因 | 解决方法 |
|---|---|---|
| 内存泄漏 | 长期保存外部变量引用 | 手动释放引用(obj = null) |
| 调试困难 | 变量被保留但不销毁 | 谨慎使用闭包做缓存 |
| 性能问题 | 作用域链查找更深 | 避免深层嵌套函数 |
✅ 一句话总结:
闭包 = 函数 + 其定义时的词法环境。 它让函数在“外部执行时”,仍能记住“内部定义时”的变量。
是否希望我帮你画一个「闭包执行过程」的内存示意图?(展示作用域链的关系)