«

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 被保留,且外部无法直接改动,形成私有状态。

四、常见应用场景

  1. 数据私有化:变量只能通过返回的方法访问,避免污染全局;
  2. 函数工厂:批量生成带不同配置的函数;
  3. 回调与事件监听:定时器、点击回调记住当时的环境变量。
    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 每轮生成新绑定,各自闭包保存对应值。

六、闭包的代价与注意

七、常见误区

  1. 以为闭包只是“函数套函数”,关键是对外层变量的持续引用;
  2. 以为返回函数后外层变量会自动清空,实际被闭包留住;
  3. 混淆作用域链查找方向,误以为内层能直接改外层;
  4. 把闭包当万能优化,忽略内存开销。

八、总结

闭包 = 函数 + 它引用的外层词法作用域。作用域链决定变量从内向外如何查找,闭包让外层变量在函数退出后仍然可访问。它用于私有化状态和函数工厂,但要警惕内存保留,循环场景优先用 let。理解闭包,才算真正懂了 JavaScript 的函数式特性。

标签: JavaScript 前端开发 闭包 作用域链