JavaScript中如何实现函数缓存?函数缓存有哪些应用场景?
函数缓存(Function Caching)是一种重要的性能优化技术,通过缓存函数的计算结果来避免重复计算,从而提高程序执行效率。本文将详细介绍JavaScript中函数缓存的实现方法和应用场景。
什么是函数缓存?
函数缓存,也称为记忆化(Memoization),是一种优化技术,它将函数的输入参数与对应的输出结果存储起来,当相同的参数再次传入时,直接返回缓存的结果,而不需要重新计算。
基础实现方法
1. 简单的缓存实现
javascript
function memoize(fn) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('从缓存中获取结果');
return cache.get(key);
}
console.log('计算新结果');
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// 使用示例
const expensiveFunction = memoize(function(n) {
console.log(`计算 ${n} 的阶乘`);
let result = 1;
for (let i = 2; i <= n; i++) {
result *= i;
}
return result;
});
console.log(expensiveFunction(5)); // 计算新结果
console.log(expensiveFunction(5)); // 从缓存中获取结果
2. 使用WeakMap优化内存
javascript
function memoizeWithWeakMap(fn) {
const cache = new WeakMap();
return function (obj, ...args) {
if (!cache.has(obj)) {
cache.set(obj, new Map());
}
const objCache = cache.get(obj);
const key = JSON.stringify(args);
if (objCache.has(key)) {
return objCache.get(key);
}
const result = fn.apply(this, [obj, ...args]);
objCache.set(key, result);
return result;
};
}
3. 支持缓存过期时间的实现
javascript
function memoizeWithExpiry(fn, ttl = 60000) { // 默认1分钟过期
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
const now = Date.now();
if (cache.has(key)) {
const { value, timestamp } = cache.get(key);
if (now - timestamp < ttl) {
return value;
}
cache.delete(key);
}
const result = fn.apply(this, args);
cache.set(key, { value: result, timestamp: now });
return result;
};
}
高级实现技巧
1. 递归函数的缓存
javascript
function memoizeRecursive(fn) {
const cache = new Map();
const memoized = function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
// 将memoized函数作为fn的参数,实现递归缓存
return function (...args) {
return memoized.apply(this, args);
};
}
// 斐波那契数列示例
const fibonacci = memoizeRecursive(function(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
});
console.log(fibonacci(50)); // 快速计算,避免重复计算
2. 异步函数的缓存
javascript
function memoizeAsync(fn) {
const cache = new Map();
return async function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
const cached = cache.get(key);
if (cached.status === 'fulfilled') {
return cached.value;
} else if (cached.status === 'rejected') {
throw cached.reason;
}
}
const promise = fn.apply(this, args);
cache.set(key, { status: 'pending', promise });
try {
const result = await promise;
cache.set(key, { status: 'fulfilled', value: result });
return result;
} catch (error) {
cache.set(key, { status: 'rejected', reason: error });
throw error;
}
};
}
// 使用示例
const fetchUserData = memoizeAsync(async (userId) => {
const response = await fetch(`/api/users/${userId}`);
return response.json();
});
应用场景
1. 计算密集型操作
javascript
// 素数判断缓存
const isPrime = memoize(function(n) {
if (n < 2) return false;
for (let i = 2; i <= Math.sqrt(n); i++) {
if (n % i === 0) return false;
}
return true;
});
// 大量重复计算时性能提升显著
for (let i = 0; i < 1000; i++) {
isPrime(997); // 第一次计算,后续从缓存获取
}
2. API请求缓存
javascript
const cachedFetch = memoizeWithExpiry(async (url) => {
const response = await fetch(url);
return response.json();
}, 5 * 60 * 1000); // 5分钟缓存
// 避免重复请求相同的数据
const userData = await cachedFetch('/api/user/123');
3. DOM查询缓存
javascript
const getElementById = memoize(function(id) {
return document.getElementById(id);
});
// 避免重复的DOM查询
const button = getElementById('submit-btn');
4. 配置解析缓存
javascript
const parseConfig = memoize(function(configString) {
// 复杂的配置解析逻辑
return JSON.parse(configString);
});
// 相同配置字符串只解析一次
const config1 = parseConfig('{"theme": "dark", "lang": "zh"}');
const config2 = parseConfig('{"theme": "dark", "lang": "zh"}'); // 从缓存获取
5. 数据转换缓存
javascript
const formatDate = memoize(function(timestamp, format) {
const date = new Date(timestamp);
// 复杂的日期格式化逻辑
return date.toLocaleDateString('zh-CN');
});
// 相同时间戳和格式的转换只执行一次
const formatted1 = formatDate(1640995200000, 'zh-CN');
const formatted2 = formatDate(1640995200000, 'zh-CN'); // 从缓存获取
注意事项
1. 内存管理
javascript
// 设置缓存大小限制
function memoizeWithLimit(fn, limit = 100) {
const cache = new Map();
return function (...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
// 将最近使用的项移到末尾
const value = cache.get(key);
cache.delete(key);
cache.set(key, value);
return value;
}
if (cache.size >= limit) {
// 删除最久未使用的项
const firstKey = cache.keys().next().value;
cache.delete(firstKey);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
2. 参数序列化
javascript
// 自定义序列化函数
function memoizeWithCustomKey(fn, keyFn) {
const cache = new Map();
return function (...args) {
const key = keyFn ? keyFn(...args) : JSON.stringify(args);
if (cache.has(key)) {
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// 使用示例:只缓存第一个参数
const expensiveCalc = memoizeWithCustomKey(
function(a, b, c) { return a + b + c; },
function(a, b, c) { return a.toString(); }
);
性能对比
javascript
// 未缓存的斐波那契函数
function fibonacciWithoutCache(n) {
if (n <= 1) return n;
return fibonacciWithoutCache(n - 1) + fibonacciWithoutCache(n - 2);
}
// 缓存的斐波那契函数
const fibonacciWithCache = memoize(function(n) {
if (n <= 1) return n;
return fibonacciWithCache(n - 1) + fibonacciWithCache(n - 2);
});
// 性能测试
console.time('无缓存');
fibonacciWithoutCache(40);
console.timeEnd('无缓存');
console.time('有缓存');
fibonacciWithCache(40);
console.timeEnd('有缓存');
总结
函数缓存是一种强大的性能优化技术,特别适用于:
- 计算密集型操作:如数学计算、算法实现
- 重复的API请求:减少网络开销
- DOM操作:避免重复查询
- 数据转换:如日期格式化、JSON解析
- 配置处理:相同配置的重复处理
通过合理使用函数缓存,可以显著提升应用程序的性能,特别是在处理重复计算时。但需要注意内存使用和缓存策略的选择,避免内存泄漏和缓存失效问题。