memoization的简介和应用

定义

利用缓存方式减少昂贵计算所带来的性能耗费,常用语递归或频繁计算当中

举例


// 获取函数运行时间
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的情况。

你可能感兴趣的:(memoization的简介和应用)