定义
指能够访问自由变量
的函数。自由变量
是指在函数中使用的,既不是函数参数也不是函数局部变量的变量。换句话说,闭包就是能够读取其他函数内部变量的函数。
在JS中,闭包通常是指一个函数及其捆绑的周边环境(词法环境
)的组合。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。
- 词法作用域
Lexical Scoping
JS的作用域在函数定义时确定,而非执行时。闭包基于此特性实现。 - 作用域链
Scope Chain
在JS中,函数在创建时会保存一个作用域链。这个作用域链包含了函数被创建时所能访问的所有变量对象Variable Object
。当函数执行时,会创建一个活动对象(Activation Object
,包含局部变量、参数等),并将其添加到作用域链的前端。 - 垃圾回收机制 正常情况下,函数执行完毕后其局部变量会被销毁。但闭包会阻止垃圾回收器回收被引用的外部变量。
形成
当一个函数返回另一个函数,且返回的函数中使用了外层函数的变量,这时就形成了一个闭包。因为返回的函数持有外层函数作用域的引用,所以外层函数的作用域不会被销毁,即使外层函数已经执行完毕。
const outer = () => {
const outerVar = "outer";
const inner = () => {
console.log(outerVar)
};
return inner;
}
const closure = outer();
closure();
- ✅
inner
引用了outerVar
- ✅
inner
被返回并在外部调用
作用
- 封装私有变量:通过闭包可以创建私有变量,只能通过特定的方法访问。
- 保持状态: 闭包可以让变量的值始终保持在内存中,不会垃圾回收机制回收。
- 模块化:闭包是实现模块模式的基础,可以创建独立的模块,避免全局污染。
内存管理
由于闭包会使得函数中的变量被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能会导致内存泄漏
。解决方法是,在退出函数之前,将不使用的局部变量全部删除(设置为null
)。
性能影响
闭包访问外部变量比访问局部变量慢,在性能关键代码中需谨慎使用。
应用实例
计数器
function createCounter() {
let count = 0;
return function() {
return count++;
};
}
const counter = createCounter();
console.log(counter()); // 0
console.log(counter()); // 1
console.log(counter()); // 2
私有方法
使用闭包模拟私有方法:
copnst counter = (function() {
let privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
};
})();
console.log(counter.value()); // 0
counter.increment();
counter.increment();
console.log(counter.value()); // 2
counter.decrement();
console.log(counter.value()); // 1
常见问题
在循环中使用闭包可能会遇到一个经典问题:
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 输出5个5
}, 1000);
}
这是因为setTimeout
的回调函数在循环结束后才执行,此时i
的值已经是5。解决方法是使用闭包为每次循环创建一个独立的作用域:
// 使用IIFE(立即执行函数表达式)创建闭包
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(function() {
console.log(j);
}, 1000);
})(i);
}
或者使用ES6的let
关键字(块级作用域):
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 输出0,1,2,3,4
}, 1000);
}
最佳实践
- 优先使用块级作用域
let
/const
- 明确解除不再需要的闭包引用
- 复杂状态管理改用
class
/模块