闭包设计模式
概述
闭包是 JavaScript 中的一个重要概念,它允许内层函数访问外层函数的变量。在实际开发中,闭包经常被用于实现特定的设计模式,主要包括辅助函数模式和工厂模式。
1. 辅助函数模式(Helper Function Pattern)
基本结构
function mainFunction(params) {
// 外层变量:状态存储
let sharedState = initialValue;
function helperFunction(helperParams) {
// 内层函数:核心逻辑实现
// 可以访问和修改 sharedState
}
// 调用辅助函数
helperFunction(params);
return sharedState; // 返回处理结果
}
特点分析
- 目的:代码组织和私有化
- 生命周期:单次调用期间
- 状态管理:临时状态,每次调用重新初始化
- 访问权限:辅助函数无法被外部直接访问
- 使用场景:递归算法、复杂逻辑分解
实际应用示例
1. 数组扁平化
function flatArr(list) {
let result = [];
function flat(currentList) { // 私有辅助函数
for (let i = 0; i < currentList.length; i++) {
if (!Array.isArray(currentList[i])) {
result.push(currentList[i]);
} else {
flat(currentList[i]); // 递归调用
}
}
}
flat(list);
return result;
}
// 使用示例
console.log(flatArr([1, [2, 3], [4, [5, 6]]])); // [1, 2, 3, 4, 5, 6]
2. 深度优先搜索
function dfsCollect(tree) {
let result = [];
function dfs(node) { // 私有辅助函数
if (!node) return;
result.push(node.value);
if (node.children) {
node.children.forEach(dfs);
}
}
dfs(tree);
return result;
}
3. 对象深拷贝
function deepClone(obj) {
let visited = new WeakMap(); // 解决循环引用
function clone(current) { // 私有辅助函数
if (current === null || typeof current !== 'object') {
return current;
}
if (visited.has(current)) {
return visited.get(current);
}
const result = Array.isArray(current) ? [] : {};
visited.set(current, result);
for (let key in current) {
if (current.hasOwnProperty(key)) {
result[key] = clone(current[key]);
}
}
return result;
}
return clone(obj);
}
2. 工厂模式 + 装饰器模式(Factory + Decorator Pattern)
基本结构
function createEnhancedFunction(originalFunction) {
// 外层变量:持久状态
const persistentState = {};
return function enhancedFunction(...args) {
// 增强逻辑:使用持久状态提供额外功能
// 调用原函数并返回结果
return originalFunction(...args);
};
}
特点分析
- 目的:增强函数功能,状态管理
- 生命周期:长期存在,跨多次调用
- 状态管理:持久状态,每个实例独立维护
- 访问权限:返回的函数可以被外部调用
- 使用场景:缓存、节流、防抖、日志记录
实际应用示例
1. 函数缓存(记忆化)
function createCache(fn) {
const cache = new Map(); // 持久缓存状态
return function (...args) { // 增强的函数
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log(`Cache hit for args: ${key}`);
return cache.get(key);
}
console.log(`Cache miss for args: ${key}`);
const result = fn(...args);
cache.set(key, result);
return result;
};
}
// 使用示例
const fibonacci = createCache(function(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
console.log(fibonacci(10)); // 计算并缓存
console.log(fibonacci(10)); // 从缓存获取
2. 函数节流
function createThrottle(delay) {
let lastTime = 0; // 持久时间状态
return function(fn) { // 增强的函数
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
lastTime = now;
return fn.apply(this, args);
}
};
};
}
// 使用示例
const throttle = createThrottle(1000);
const throttledLog = throttle(console.log);
3. 函数调用计数器
function createCounter(fn) {
let count = 0; // 持久计数状态
return function(...args) { // 增强的函数
count++;
console.log(`函数被调用了 ${count} 次`);
return fn.apply(this, args);
};
}
// 使用示例
const countedAdd = createCounter((a, b) => a + b);
console.log(countedAdd(1, 2)); // 函数被调用了 1 次 \n 3
console.log(countedAdd(3, 4)); // 函数被调用了 2 次 \n 7
4. 函数执行时间统计
function createTimer(fn) {
const stats = { totalTime: 0, callCount: 0 }; // 持久统计状态
return function(...args) { // 增强的函数
const start = Date.now();
const result = fn.apply(this, args);
const end = Date.now();
stats.totalTime += (end - start);
stats.callCount++;
stats.averageTime = stats.totalTime / stats.callCount;
console.log(`执行时间: ${end - start}ms, 平均时间: ${stats.averageTime}ms`);
return result;
};
}
3. 两种模式对比
特性 | 辅助函数模式 | 工厂模式 + 装饰器模式 |
---|---|---|
主要目的 | 代码组织和逻辑分解 | 功能增强和状态管理 |
状态生命周期 | 单次调用期间 | 跨多次调用持久存在 |
状态初始化 | 每次调用重新初始化 | 创建时初始化,后续复用 |
函数可见性 | 辅助函数私有 | 返回增强后的公开函数 |
使用方式 | 直接调用主函数 | 先创建增强函数,再调用 |
典型应用 | 递归算法、复杂计算 | 缓存、节流、装饰器 |
4. 选择指南
使用辅助函数模式的场景:
- 需要将复杂逻辑分解为多个步骤
- 实现递归算法时需要维护中间状态
- 希望隐藏实现细节,只暴露主要接口
- 需要在单次执行中共享临时变量
使用工厂模式 + 装饰器模式的场景:
- 需要为现有函数添加额外功能
- 需要维护跨调用的持久状态
- 实现缓存、节流、防抖等功能增强
- 需要创建多个具有相似功能但独立状态的函数
5. 常见问题解答(FAQ)
Q: 多个 createCache 实例会造成缓存冲突吗?
A: 不会冲突。每次调用 createCache(fn)
都会创建一个全新的闭包环境,每个返回的函数都有自己独立的 cache
变量。
原理解释
function createCache(fn) {
const cache = new Map(); // 每次调用createCache都会创建新的cache
return function (...args) {
// 这个返回的函数只能访问它所属闭包环境中的cache
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log(`Cache hit for ${fn.name}: ${key}`);
return cache.get(key);
}
console.log(`Cache miss for ${fn.name}: ${key}`);
const result = fn(...args);
cache.set(key, result);
return result;
};
}
实际验证示例
// 创建两个不同的缓存函数
const cachedAdd = createCache(function add(a, b) {
return a + b;
});
const cachedMultiply = createCache(function multiply(a, b) {
return a * b;
});
// 测试:相同参数但不同函数
console.log('=== 第一次调用 ===');
console.log(cachedAdd(2, 3)); // Cache miss for add: [2,3] -> 5
console.log(cachedMultiply(2, 3)); // Cache miss for multiply: [2,3] -> 6
console.log('=== 第二次调用相同参数 ===');
console.log(cachedAdd(2, 3)); // Cache hit for add: [2,3] -> 5
console.log(cachedMultiply(2, 3)); // Cache hit for multiply: [2,3] -> 6
// 结果:每个函数都有自己独立的缓存,互不影响
闭包作用域独立性
// 每次调用 createCache 时的内存模型:
const fn1 = createCache(addFunction); // 创建闭包环境1,有独立的cache1
const fn2 = createCache(multiplyFunction); // 创建闭包环境2,有独立的cache2
// 内存结构示意:
/*
闭包环境1: {
cache: Map { [2,3] => 5 } // 独立的缓存空间1
return function1
}
闭包环境2: {
cache: Map { [2,3] => 6 } // 独立的缓存空间2
return function2
}
*/
// fn1 只能访问 cache1
// fn2 只能访问 cache2
// 它们完全独立,互不影响
需要注意的场景
// 同一个函数创建多个缓存实例
const fibonacci = function(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
};
const cache1 = createCache(fibonacci);
const cache2 = createCache(fibonacci);
// 这样会创建两个独立的缓存,可能造成重复计算
console.log(cache1(10)); // 第一个缓存计算
console.log(cache2(10)); // 第二个缓存重新计算(因为缓存是独立的)
最佳实践
// 1. 单例模式:对于同一个函数,通常只创建一个缓存实例
const cachedFibonacci = createCache(function fibonacci(n) {
if (n <= 1) return n;
return cachedFibonacci(n - 1) + cachedFibonacci(n - 2);
});
// 2. 带清理功能的缓存
function createCacheWithClear(fn) {
const cache = new Map();
const cachedFn = function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn(...args);
cache.set(key, result);
return result;
};
// 添加清理方法
cachedFn.clearCache = () => cache.clear();
cachedFn.getCacheSize = () => cache.size;
return cachedFn;
}
// 使用示例
const smartCache = createCacheWithClear((x) => x * x);
console.log(smartCache(5)); // 25
console.log(smartCache.getCacheSize()); // 1
smartCache.clearCache();
console.log(smartCache.getCacheSize()); // 0
6. 最佳实践
性能考虑
- 辅助函数模式:避免在频繁调用的函数中使用复杂的辅助逻辑
- 工厂模式:注意内存泄漏,合理管理持久状态的生命周期
可读性优化
- 使用描述性的函数名称
- 添加适当的注释说明闭包的用途
- 保持函数职责单一
调试技巧
- 在开发环境中添加日志输出
- 使用浏览器开发者工具的断点调试
- 注意闭包可能导致的内存占用
总结
闭包设计模式是 JavaScript 中强大的编程工具,通过合理运用辅助函数模式和工厂模式,可以写出更加优雅、可维护的代码。关键是要根据具体的使用场景选择合适的模式,并注意性能和内存管理的最佳实践。
核心要点:
- 每个闭包创建独立的作用域环境
- 工厂函数返回的每个实例都有独立的状态
- 合理使用闭包可以实现强大的功能增强
- 注意内存管理和性能优化