闭包是JavaScript中最让人困惑的概念之一。面试必问,但很多人背了定义还是不太理解。
今天用最直白的方式把闭包讲透。
一句话解释闭包
闭包就是函数记住了它被创建时的环境,即使在那个环境之外执行。
function outer() {
let count = 0;
function inner() {
count++;
console.log(count);
}
return inner;
}
const fn = outer();
fn(); // 1
fn(); // 2
fn(); // 3
// outer()已经执行完了,但inner还能访问count
// 这就是闭包
从作用域链理解
JavaScript有词法作用域——函数能访问定义时所在的变量,而不是调用时。
let x = 10;
function foo() {
let y = 20;
function bar() {
let z = 30;
console.log(x + y + z); // 60
// bar能访问x(全局)、y(foo的)、z(自己的)
}
bar();
}
foo();
当foo返回bar的引用后,foo的执行上下文虽然被销毁了,但y变量不会被垃圾回收——因为bar还在引用它。这就是闭包的本质。
闭包的经典应用
应用一:数据私有化(模块模式)
function createCounter() {
let count = 0; // 外部无法直接访问
return {
increment() { count++; },
decrement() { count--; },
getCount() { return count; }
};
}
const counter = createCounter();
counter.increment();
counter.increment();
console.log(counter.getCount()); // 2
// count变量被"封闭"在闭包里,外部无法直接修改
应用二:函数工厂
function multiply(factor) {
return function(number) {
return number * factor;
};
}
const double = multiply(2);
const triple = multiply(3);
console.log(double(5)); // 10
console.log(triple(5)); // 15
应用三:防抖节流(前面聊过的)
function debounce(fn, delay) {
let timer = null; // 被闭包记住
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => fn.apply(this, args), delay);
};
}
闭包的经典陷阱
陷阱一:循环中的闭包
// 错误示范
for (var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 输出5个5!
}, 100);
}
// 因为var声明的i是函数作用域的,所有回调共享同一个i
// 等定时器执行时,循环已经结束,i=5
// 解决一:用let(块级作用域)
for (let i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i); // 0, 1, 2, 3, 4
}, 100);
}
// 解决二:IIFE创建闭包
for (var i = 0; i < 5; i++) {
(function(j) {
setTimeout(function() {
console.log(j); // 0, 1, 2, 3, 4
}, 100);
})(i);
}
陷阱二:内存泄漏
function bindEvent() {
const hugeData = new Array(1000000); // 大量数据
document.getElementById('btn').onclick = function() {
// 闭包引用了hugeData,即使按钮被移除,hugeData也不会被回收
console.log(hugeData.length);
};
}
解决办法:不需要时手动释放引用。
闭包和性能
闭包会让被引用的变量无法被垃圾回收,所以不要滥用。
| 场景 | 是否推荐 |
|---|---|
| 模块模式/数据私有化 | 推荐 |
| 函数工厂/柯里化 | 推荐 |
| 回调中引用外部变量 | 正常用法 |
| 大量闭包引用大对象 | 不推荐,可能内存泄漏 |
闭包其实不复杂——函数记住了创建时的变量,就这么简单。关键是理解它在实际场景中的应用,以及注意内存问题。
你们在项目中哪些地方用到了闭包?