0%

js闭包

闭包是指函数与其词法环境的组合,也就是内部函数可以访问外部函数作用域中的变量,即使外部函数已经执行完毕。

什么是闭包?

在 javascript 中,每当创建一个函数,就会形成闭包。更准确地说,闭包是由函数以及该函数被声明时所在的作用域共同组成的。这个作用域包含了函数内部引用的所有外部变量。

看一个最简单的闭包示例:

1
2
3
4
5
6
7
8
9
10
function outer() {
const message = 'Hello, Closure!';
function inner() {
console.log(message);
}
return inner;
}

const closureFn = outer();
closureFn(); // 输出 'Hello, Closure!'

在这个例子中,inner 函数使用了外部函数 outer 的变量 message。当 outer 执行完毕返回 inner 后,按照常理 outer 的作用域应该被销毁,但由于 inner 仍然持有对 message 的引用,javascript 引擎会保留这个变量,使得 inner 可以在外部继续访问它。这个 inner 函数及其引用的环境就构成了一个闭包。

闭包的原理

闭包的形成依赖于 javascript 的词法作用域(静态作用域)和垃圾回收机制。

  1. 词法作用域:函数的作用域在定义时就已经确定,而不是在执行时确定。因此,内部函数总能访问外部函数中声明的变量。
  2. 垃圾回收:通常,当函数执行完后,其局部变量会被标记为可回收。但如果还有别的函数(如内部函数)仍然引用这些变量,它们就不会被回收,从而继续存在。

闭包常见的应用场景

创建私有变量

javascript 没有真正的私有成员,但可以利用闭包模拟私有变量。通过函数作用域隐藏变量,只暴露特定方法进行操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function createCounter() {
let count = 0;
return {
increment() {
count++;
console.log(count);
},
decrement() {
count--;
console.log(count);
},
getCount() {
return count;
}
};
}

const counter = createCounter();
counter.increment(); // 1
counter.increment(); // 2
console.log(counter.getCount()); // 2
// 无法直接访问 count
console.log(counter.count); // undefined

函数工厂

闭包可以用于生成具有特定行为的函数,例如根据参数创建不同的处理函数。

1
2
3
4
5
6
7
8
9
10
function multiplyBy(factor) {
return function(number) {
return number * factor;
};
}

const double = multiplyBy(2);
const triple = multiplyBy(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15

回调函数与事件处理

在异步操作或事件监听中,闭包常用于保留状态。

1
2
3
4
5
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 输出 3 3 3
}, 100);
}

上面的例子中,由于 var 没有块级作用域,所有回调共享同一个 i,循环结束后 i 变为 3,因此输出三个 3。利用闭包可以解决这个问题:

1
2
3
4
5
6
7
for (var i = 0; i < 3; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 输出 0 1 2
}, 100);
})(i);
}

或者使用 let 创建块级作用域:

1
2
3
4
5
for (let i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 输出 0 1 2
}, 100);
}

模块化

在 ES6 模块出现之前,闭包常用于实现模块模式,隔离内部实现,只暴露公共 API。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var myModule = (function() {
var privateVar = 'I am private';
function privateMethod() {
console.log('private method');
}
return {
publicMethod: function() {
console.log(privateVar);
privateMethod();
}
};
})();

myModule.publicMethod(); // I am private \n private method

闭包的内存管理

由于闭包会持续引用外部函数的变量,这些变量无法被垃圾回收,如果使用不当可能导致内存泄漏。例如,在不需要闭包时,仍然保持引用:

1
2
3
4
5
6
7
function heavyProcess() {
const largeData = new Array(1000000).fill('data');
return function() {
console.log(largeData.length);
};
}
const leak = heavyProcess(); // largeData 一直存在

上例中 largeData 无法被回收,因为闭包一直引用它。如果不再需要闭包,应该将引用设为 null:

1
leak = null; // 解除引用,允许垃圾回收

闭包与性能

闭包虽然强大,但过度使用可能带来性能问题,因为每个闭包都额外维护自己的作用域链。在频繁创建大量闭包的场景(如循环中创建函数)时需谨慎,尽量重用函数。

总结

  1. 闭包 = 函数 + 函数定义时的词法环境。
  2. 内部函数可以访问外部函数的变量,即使外部函数已经返回。
  3. 常见用途:私有变量、函数工厂、回调、模块化。
  4. 注意内存管理,及时释放不再需要的闭包。
  5. 现代 javascript 中,let/const 和箭头函数可以简化某些闭包场景,但闭包的核心思想依然不变。