什么是闭包?
闭包是指能够访问并记住其词法作用域的函数,即使这个函数在其词法作用域之外执行。
简单示例
function outer() {
let count = 0; // 局部变量
// 内部函数形成了闭包
function inner() {
count++; // 访问外部函数的变量
console.log(count);
}
return inner; // 返回内部函数
}
const counter = outer(); // outer 执行完毕,count 应该被销毁?
counter(); // 1 - 但 count 仍然存在!
counter(); // 2
counter(); // 3
在这个例子中,inner函数就是一个闭包,它记住了外层函数 outer的变量 count。
闭包的形成条件
- 函数嵌套:一个函数在另一个函数内部
- 内部函数引用外部函数的变量
- 内部函数在外部函数之外被调用
function createClosure() {
let message = "Hello"; // 闭包会"记住"这个变量
return function(name) {
console.log(`${message}, ${name}!`); // 访问外部变量
};
}
const greet = createClosure();
greet("Alice"); // "Hello, Alice!"
greet("Bob"); // "Hello, Bob!"
闭包的常见用途
1. 数据封装和私有变量
// 传统对象没有真正的私有属性
const person = {
_name: "Alice", // 下划线约定,但实际还是可访问
getName() {
return this._name;
}
};
console.log(person._name); // 可以直接访问
// 使用闭包实现真正的私有变量
function createPerson(name) {
let privateName = name; // 真正的私有变量
return {
getName() {
return privateName;
},
setName(newName) {
privateName = newName;
}
};
}
const alice = createPerson("Alice");
console.log(alice.getName()); // "Alice"
console.log(alice.privateName); // undefined - 无法直接访问
alice.setName("Bob");
console.log(alice.getName()); // "Bob"
2. 创建函数工厂
// 创建幂函数生成器
function powerCreator(exponent) {
return function(base) {
return Math.pow(base, exponent);
};
}
const square = powerCreator(2);
const cube = powerCreator(3);
console.log(square(5)); // 25
console.log(cube(3)); // 27
console.log(square(4)); // 16
3. 实现模块模式
const calculator = (function() {
// 私有变量
let memory = 0;
let operationCount = 0;
// 私有函数
function incrementCount() {
operationCount++;
}
// 返回公共接口
return {
add: function(a, b) {
incrementCount();
return a + b;
},
subtract: function(a, b) {
incrementCount();
return a - b;
},
getCount: function() {
return operationCount;
},
clearMemory: function() {
memory = 0;
}
};
})();
console.log(calculator.add(5, 3)); // 8
console.log(calculator.getCount()); // 1
console.log(calculator.memory); // undefined - 私有变量
4. 缓存和记忆化
function createMemoizedFunction(fn) {
const cache = {}; // 闭包缓存
return function(...args) {
const key = JSON.stringify(args);
if (cache[key] !== undefined) {
console.log('从缓存读取');
return cache[key];
}
console.log('计算结果');
const result = fn(...args);
cache[key] = result;
return result;
};
}
// 一个计算量大的函数
function slowCalculation(n) {
console.log(`计算 ${n} 的平方...`);
return n * n;
}
const memoizedCalc = createMemoizedFunction(slowCalculation);
console.log(memoizedCalc(5)); // 计算 5 的平方... 25
console.log(memoizedCalc(5)); // 从缓存读取 25
console.log(memoizedCalc(6)); // 计算 6 的平方... 36
5. 事件处理和回调
function setupButtons() {
const buttons = document.querySelectorAll('button');
buttons.forEach(function(button, index) {
// 闭包记住了每个按钮的 index
button.addEventListener('click', function() {
console.log(`按钮 ${index} 被点击`);
});
});
}
// 不使用闭包会有问题
function setupButtonsWrong() {
const buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', function() {
console.log(`按钮 ${i} 被点击`); // 总是输出最后一个 i
});
}
}
6. 实现柯里化
// 柯里化:将一个多参数函数转换为一系列单参数函数
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
return fn.apply(this, args);
} else {
return function(...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}
// 使用示例
function addThree(a, b, c) {
return a + b + c;
}
const curriedAdd = curry(addThree);
const addFive = curriedAdd(2)(3);
console.log(addFive(5)); // 10
console.log(addFive(10)); // 15
闭包的原理
作用域链
function outer() {
let x = 10;
function middle() {
let y = 20;
function inner() {
let z = 30;
// inner 的作用域链:inner → middle → outer → global
console.log(x + y + z); // 可以访问所有外部变量
}
return inner;
}
return middle;
}
const innerFn = outer()(); // 得到 inner 函数
innerFn(); // 60
闭包的内存管理
function createClosures() {
let largeData = new Array(1000000).fill('data');
let count = 0;
return {
increment: function() {
count++;
console.log(count);
},
getData: function() {
return largeData.length;
}
};
}
// 注意:即使只使用 increment,largeData 也不会被回收
// 因为闭包保持了整个作用域链的引用
const obj = createClosures();
obj.increment(); // largeData 仍在内存中!
// 正确做法:及时清理不需要的引用
function createOptimizedClosure() {
let largeData = new Array(1000000).fill('data');
let count = 0;
const increment = function() {
count++;
console.log(count);
};
// 如果 largeData 不再需要,可以显式释放
const cleanup = function() {
largeData = null; // 帮助垃圾回收
};
return { increment, cleanup };
}
实际应用案例
1. React Hooks 的核心
// useState 的简化实现原理
function useState(initialValue) {
let state = initialValue;
const setState = (newValue) => {
state = newValue;
// 触发重新渲染
render();
};
return [state, setState]; // 闭包记住了 state
}
2. 防抖和节流
// 防抖函数
function debounce(fn, delay) {
let timer; // 闭包保存 timer
return function(...args) {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 节流函数
function throttle(fn, interval) {
let lastTime = 0; // 闭包保存上次执行时间
return function(...args) {
const now = Date.now();
if (now - lastTime >= interval) {
fn.apply(this, args);
lastTime = now;
}
};
}
3. 状态管理
function createStore(reducer, initialState) {
let state = initialState;
let listeners = [];
const getState = () => state;
const dispatch = (action) => {
state = reducer(state, action);
listeners.forEach(listener => listener());
};
const subscribe = (listener) => {
listeners.push(listener);
return () => {
listeners = listeners.filter(l => l !== listener);
};
};
return { getState, dispatch, subscribe };
}
注意事项和最佳实践
1. 避免意外的闭包
// ❌ 意外的闭包导致内存泄漏
function createProblem() {
const largeData = new Array(1000000);
setInterval(() => {
console.log('定时器运行'); // 闭包引用了 largeData
}, 1000);
// largeData 不会被回收,因为定时器回调形成了闭包
}
// ✅ 解决方案
function createSolution() {
const largeData = new Array(1000000);
// 立即使用并释放
processData(largeData);
largeData.length = 0; // 帮助 GC
setInterval(() => {
console.log('定时器运行');
}, 1000);
}
2. 性能优化
// ❌ 在循环中创建闭包
function slowFunction() {
for (let i = 0; i < 10000; i++) {
element.addEventListener('click', function() {
console.log(i); // 每次循环都创建新闭包
});
}
}
// ✅ 优化版本
function fastFunction() {
const handler = function(e) {
const index = e.target.dataset.index;
console.log(index);
};
for (let i = 0; i < 10000; i++) {
element.addEventListener('click', handler);
}
}
闭包的作用总结
| 用途 | 说明 | 示例 |
|---|---|---|
| 数据封装 | 创建私有变量,实现信息隐藏 | 模块模式 |
| 状态保持 | 函数"记住"之前的状态 | 计数器、缓存 |
| 函数工厂 | 动态生成特定功能的函数 | 柯里化、防抖函数 |
| 事件处理 | 保持事件处理器的上下文 | 事件监听器 |
| 延迟执行 | 异步操作保持状态 | Promise、setTimeout 回调 |
| 模块化 | 组织代码,避免污染全局 | IIFE 模块 |