定义
利用缓存方式减少昂贵计算所带来的性能耗费,常用语递归或频繁计算当中
举例
// 获取函数运行时间
function getTime(fn, ...args) {
const now = Date.now()
const result = fn.apply(null, args)
console.log(`用时:${Date.now() - now}毫秒,结果:${result}`)
}
// 斐波那契数列
function fibonacci (n) {
if (n === 0) return 0
else if (n === 1) return 1
else return fibonacci(n - 1) + fibonacci(n - 2)
}
getTime(fibonacci, 40)
// memoization化斐波那契数列
const fibonacciWithCache = (function () {
// 设置缓存队列
const cacheList = [0, 1, 1, 2]
return function _fibonacciWithCache (n) {
// 如果缓存队列中没有该运算结果,则计算出结果并加入缓存队列中
if (!cacheList[n])
cacheList[n] = _fibonacciWithCache(n - 1) + _fibonacciWithCache(n - 2)
return cacheList[n]
}
}())
getTime(fibonacciWithCache, 40)
分别用非缓存与缓存方式分别实现递归方式的斐波那契数列
计算的耗费时间如下:
用时:1554毫秒,结果:102334155
用时:0毫秒,结果:102334155
前者运行了331160281次,而后者仅运行了75次
工具方法
// memoization实现
const memoize = function (fn) {
const cacheMap = {}
return function (key) {
if(!cacheMap[key]) cacheMap[key] = fn.apply(this, arguments)
return cacheMap[key]
}
}
const newFibonacci = memoize(fibonacci)
// lodash中的实现方式
const lodashMemoize = function(func, hasher) {
var memoize = function(key) {
var cache = memoize.cache;
var address = '' + (hasher ? hasher.apply(this, arguments) : key);
if (!_.has(cache, address))
cache[address] = func.apply(this, arguments);
return cache[address];
};
memoize.cache = {};
return memoize;
};
const newLodashMemoize = memoize(fibonacci)
两种方式,loadsh添加了对键值的定制化方法,缓存会绑定在返回的方法之上,虽方法的销毁一同销毁。剩下的思路都是一样的,现实中我们写工具类的时候可以用map来代替object
但需要注意的是,这种方法仅对性能耗费较大的运算有缓存优势,而对象上面实现斐波那契数列的递归方式对单次结果的返回无影响,即它只对计算结果进行了缓存,但对计算过程并没有优化。
由于递归的特殊性,在写代码时就注意memoization的性能考虑是更好的选择。
现实中应用
前端在现实项目中很少用到递归或者性能消耗较大的例子,但比如react这种状态变化会多次触发渲染的框架,应用memoization方式避免重复运算提高性能,是常见的方式。
在现实中应用需要注意的是,大量的缓存会占据内存空间,导致空间浪费。当我们不能控制好缓存的大小时,谨慎使用。
现实项目中,诸如memoize-one的插件是更好的选择,他只缓存你上一次的计算结果,这种特性在减少覆盖率的情况下也减轻了缓存的负担。并且与react特性特别的match。可以清除react本身大部分由于大量状态变动导致的频繁render的情况。