JavaScript 闭包与作用域链详解
时间:2026-11-15 07:18 作者:emer 分类: 无
JavaScript 闭包与作用域链详解
一、什么是闭包
闭包(Closure)是指函数能够记住并访问它词法作用域里的变量,即使外层函数已经执行结束。简单说:内层函数引用了外层函数的变量,外层函数返回后,这些变量不会被垃圾回收,依然存活在闭包里。
二、作用域与作用域链
JavaScript 有全局作用域、函数作用域(ES6 后还有块级作用域)。查找变量时,引擎从当前作用域开始,一层层向外找,直到全局,这条查找路径就是作用域链。
let a = '全局';
function outer() {
let b = '外层';
function inner() {
let c = '内层';
console.log(a, b, c); // 沿作用域链逐层找到
}
inner();
}
outer();
inner 找不到 b、a 时就沿链向上找。
三、最简单的闭包
function makeCounter() {
let count = 0; // 被闭包保护的私有变量
return function () {
count++;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
makeCounter 执行完本应销毁 count,但返回的匿名函数仍引用它,于是 count 被保留,且外部无法直接改动,形成私有状态。
四、常见应用场景
- 数据私有化:变量只能通过返回的方法访问,避免污染全局;
- 函数工厂:批量生成带不同配置的函数;
- 回调与事件监听:定时器、点击回调记住当时的环境变量。
function power(n) { return function (x) { return x ** n; }; } const square = power(2); console.log(square(5)); // 25
五、经典循环陷阱
// 错误:var 共享同一变量,结果全是 3
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 3 3 3
}
// 正确:let 每轮块级绑定,或用立即执行函数隔离
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 0); // 0 1 2
}
var 声明的 i 挂在函数作用域,三个回调闭包引用同一个 i;let 每轮生成新绑定,各自闭包保存对应值。
六、闭包的代价与注意
- 被闭包引用的变量不会回收,滥用会造成内存占用上升;
- 不需要的闭包及时把引用置 null,方便 GC;
- 闭包里的 this 指向由调用方式决定,不继承外层,需要保存时常用
const self = this; - 不要在循环里盲目创建大量闭包,避免性能损耗。
七、常见误区
- 以为闭包只是“函数套函数”,关键是对外层变量的持续引用;
- 以为返回函数后外层变量会自动清空,实际被闭包留住;
- 混淆作用域链查找方向,误以为内层能直接改外层;
- 把闭包当万能优化,忽略内存开销。
八、总结
闭包 = 函数 + 它引用的外层词法作用域。作用域链决定变量从内向外如何查找,闭包让外层变量在函数退出后仍然可访问。它用于私有化状态和函数工厂,但要警惕内存保留,循环场景优先用 let。理解闭包,才算真正懂了 JavaScript 的函数式特性。
标签: JavaScript 前端开发 闭包 作用域链