在 JavaScript 中,闭包(Closure) 是指一个函数能够记住并访问它的词法作用域(Lexical Scope),即使这个函数在其作用域之外被执行。闭包的特性使得它在以下情况下非常有用:

适用情况

  • 封装私有数据:在 JavaScript 中没有内建的「私有属性」,闭包可以模拟这种行为,让一些数据无法被外部直接访问。
  • 创建工厂函数(Factory Functions):使用闭包构造多个具有相似行为的对象。
  • 模拟状态(Stateful Functions):闭包允许函数在多次调用之间保存数据,适合用于实现累加器等功能。
  • 处理异步操作:闭包可以让回调函数记住当前的上下文,这在事件处理或异步操作中非常重要。
  • 函数柯里化(Currying):闭包用于将多参数函数转化为单参数函数链式调用。
  • 闭包范例

    范例 1:封装私有数据

    使用闭包模拟私有属性,让外部无法直接修改内部数据。

    function createCounter() {
    let count = 0; // 私有变量
    return {
    increment() {
    count++;
    },
    decrement() {
    count--;
    },
    getCount() {
    return count;
    }
    };
    }

    const counter = createCounter();
    console.log(counter.getCount()); // 0
    counter.increment();
    counter.increment();
    console.log(counter.getCount()); // 2
    counter.decrement();
    console.log(counter.getCount()); // 1

    范例 2:模拟状态的工厂函数

    使用闭包创建具有不同初始状态的计数器。

    function createCounter(initialValue) {
    let count = initialValue;
    return function () {
    count++;
    return count;
    };
    }

    const counterA = createCounter(5);
    const counterB = createCounter(10);

    console.log(counterA()); // 6
    console.log(counterA()); // 7
    console.log(counterB()); // 11
    console.log(counterB()); // 12

    范例 3:处理异步操作

    闭包在回调函数中保存上下文数据。

    function fetchData(url) {
    let cache = {};

    return function () {
    if (cache[url]) {
    console.log("Fetching from cache:", cache[url]);
    return cache[url];
    } else {
    console.log("Fetching from server...");
    const data = `Data from ${url}`; // 模拟服务器数据
    cache[url] = data;
    return data;
    }
    };
    }

    const getUserData = fetchData("https://api.example.com/user");
    console.log(getUserData()); // Fetching from server...
    console.log(getUserData()); // Fetching from cache: Data from https://api.example.com/user

    范例 4:函数柯里化

    闭包让函数变成更灵活的形式。

    function multiply(a) {
    return function (b) {
    return a * b;
    };
    }

    const double = multiply(2); // 创建一个函数,固定第一个参数为 2
    const triple = multiply(3); // 创建一个函数,固定第一个参数为 3

    console.log(double(4)); // 8
    console.log(triple(4)); // 12

    范例 5:事件处理中的闭包

    闭包可以让事件处理器记住它绑定时的上下文。

    function setupButtons() {
    for (let i = 1; i <= 3; i++) {
    document.getElementById(`button${i}`).addEventListener("click", function () {
    console.log(`Button ${i} clicked!`);
    });
    }
    }

    在这里,闭包使得每个事件处理器记住了绑定时的 i 值。

    注意事项

  • 内存占用:由于闭包引用了外部变量,这些变量无法被垃圾回收器回收,可能导致内存泄漏。需要在不需要时明确清理闭包引用。
  • 可能引起意料之外的行为:在循环中使用闭包时需要注意作用域问题,避免共用同一个变量。
  • 总结

    闭包适合用于:

    • 封装私有数据
    • 保存状态
    • 创建灵活的工厂函数或柯里化
    • 处理异步操作和事件绑定闭包是 JavaScript 中强大而灵活的特性,适用于需要「记住某些信息」的场景。