Skip to content

JavaScript 闭包详解

1. 基本概念

闭包(Closure)它允许函数访问并操作其外部作用域中的变量。简单来说,闭包就是一个函数能够记住并访问它的词法作用域,即使当这个函数在其原始作用域之外执行时。

2. 基础示例

javascript
function createGreeting(name) {
    const message = "Hello, ";
    
    return function() {
        console.log(message + name); // 访问外部函数的变量
    }
}

const greetJohn = createGreeting("John");
greetJohn(); // 输出: Hello, John

解释:

  • createGreeting 函数创建了一个闭包,它包含了一个 message 变量和一个返回的匿名函数。
  • createGreeting 函数执行时,message 变量被初始化,并存储在闭包中。
  • 返回的匿名函数可以访问 message 变量,即使 createGreeting 函数已经执行完毕。
  • greetJohn 函数被调用时,它执行了返回的匿名函数,并输出 "Hello, John"。

3. 闭包的特点

3.1 数据私有化

javascript
function createBank() {
    let balance = 0;  // 私有变量
    
    return {
        deposit: function(amount) {
            balance += amount;
            return balance;
        },
        getBalance: function() {
            return balance;
        }
    };
}

const account = createBank();
console.log(account.deposit(100));  // 100
console.log(account.getBalance()); // 100
console.log(account.balance);      // undefined - 无法直接访问

解释:

  • createBank 函数创建了一个闭包,它包含了一个 balance 变量和一个返回的对象。
  • 返回的对象包含两个方法:depositgetBalance
  • deposit 方法用于增加 balance 的值,并返回新的 balance 值。
  • getBalance 方法用于返回当前的 balance 值。
  • 由于 balance 变量是私有变量,无法直接访问,会返回undifined,实现了数据的封装。-
  • 只能通过 返回的对象方法(depositgetBalance) 来访问和修改 balance 变量。

3.2 状态保持

javascript
function createCounter() {
    let count = 0;
    
    return {
        increment: function() {
            return ++count;
        },
        decrement: function() {
            return --count;
        },
        getCount: function() {
            return count;
        }
    };
}

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

解释

  • count 变量保持了计数器的状态
  • 每次调用 incrementdecrement 都会修改这个状态
  • 状态会被保持,不会被垃圾回收
  • 每个新的计数器实例都有自己的 count 变量

4. 实际应用场景

4.1 事件处理

javascript
function handleClick(element, callback) {
    let count = 0;
    
    element.addEventListener('click', function() {
        count++;
        callback(count);
    });
}

// 使用示例
handleClick(button, function(clicks) {
    console.log(`Button clicked ${clicks} times`);
});

解释

  • 创建了一个点击计数器
  • 每次点击都会增加 count
  • 闭包保持了 count 的值
  • 回调函数可以访问最新的点击次数

4.2 函数工厂

javascript
function multiply(x) {
    return function(y) {
        return x * y;
    };
}

const multiplyByTwo = multiply(2);
const multiplyByThree = multiply(3);

console.log(multiplyByTwo(4));   // 8
console.log(multiplyByThree(4)); // 12

解释

  • multiply 是一个函数工厂,用于创建特定的乘法函数
  • 返回的函数记住了 x 的值
  • multiplyByTwo 永远记住 x = 2
  • multiplyByThree 永远记住 x = 3

4.3 模块模式

javascript
const calculator = (function() {
    let result = 0;
    
    return {
        add: function(x) {
            result += x;
            return this;
        },
        subtract: function(x) {
            result -= x;
            return this;
        },
        getResult: function() {
            return result;
        }
    };
})();

calculator.add(5).subtract(2);
console.log(calculator.getResult()); // 3

解释

  • 使用立即执行函数创建模块
  • result 是私有变量
  • 返回的对象包含可以操作 result 的方法
  • 支持链式调用(通过返回 this)
  • 实现了模块化和封装

5. 注意事项

5.1 内存管理

javascript
function createLeak() {
    const largeData = new Array(1000000);
    
    return function() {
        console.log(largeData.length);
    };
}

// 可能导致内存泄漏
const leak = createLeak(); // largeData 会一直保存在内存中

5.2 循环中的闭包

javascript
// 错误示例
for (var i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}
// 输出: 3, 3, 3

// 正确示例
for (let i = 0; i < 3; i++) {
    setTimeout(function() {
        console.log(i);
    }, 1000);
}
// 输出: 0, 1, 2

解释

  • 使用 var 时,所有的定时器共享同一个 i
  • 当定时器执行时,循环已经结束,i 变成了 3
  • 使用 let 时,每次循环都会创建新的变量
  • 每个定时器都能访问到自己的 i

6. 最佳实践

javascript
// 1. 及时释放不需要的闭包
function someFunction() {
    const heavyObject = { /* ... */ };
    
    return function() {
        // 使用 heavyObject
    };
}
let closure = someFunction();
// 使用完后释放
closure = null;

// 2. 避免创建不必要的闭包
const goodExample = {
    value: 1,
    getValue() {
        return this.value;
    }
};

// 3. 使用立即执行函数创建私有作用域
const module = (function() {
    const private = 'private value';
    
    return {
        publicMethod() {
            return private;
        }
    };
})();

总结

闭包是 JavaScript 中非常强大的特性,它能够:

  1. 创建私有变量和方法
  2. 保持状态、状态维护
  3. 实现模块化
  4. 创建函数工厂

但要注意:

  • 合理使用以避免内存泄漏
  • 注意循环中的闭包陷阱以及作用域和变量提升 this绑定
  • 及时释放不需要的闭包

闭包的缺点:

  • 内存泄漏
  • 性能问题
  • 调试困难
  • 可能引起循环引用