在计算机领域,记忆(memoization)是主要用于加速程序计算的一种优化技术,它使得函数避免重复演算以前已被处理过的输入,而返回已缓存的结果。 -- wikipedia
Memoization
的原理就是把函数的每次执行结果都放入一个对象中,在接下来的执行中,在对象中查找是否已经有相应执行过的值,若是有,直接返回该值,没有才真正执行函数体的求值部分。在对象里找值是要比执行函数的速度要快的。javascript
另外,Memoization
只适用于肯定性算法,对于相同的输入老是生成相同的输出,即纯函数。java
经过 Memoization 的定义和原理,咱们就能够初步实现 Memoization 了。算法
let memoize = function(func) { let cache = {}; return function(key) { if (!cache[key]) cache[key] = func.apply(this, arguments); return cache[key]; } }
是否是很简单~ 函数记忆其实就是利用闭包,将函数参数做为存储对象的键(key),函数结果做为存储对象的 value 值。缓存
underscore 的源码中有 Memoization 方法的封装,它支持传入一个 hasher 用来计算缓存对象 key 的计算方式。闭包
_.memoize = function(func, hasher) { var memoize = function(key) { // 把存储对象的引用拿出来,便于后面代码使用 var cache = memoize.cache; // hasher 是计算 key 值的方法函数。 // 若是传入了 hasher,则用 hasher 函数来计算 key // 不然用 参数 key(即 memoize 方法传入的第一个参数)当 key var address = '' + (hasher ? hasher.apply(this, arguments) : key); // 若是 key 尚未对应的 hash 值(意味着没有缓存值,没有计算过该输入) // 就执行回调函数,并缓存结果值 if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); // 从缓存对象中取结果值 return cache[address]; }; // cache 对象被当作 key-value 键值对缓存中间运算结果 memoize.cache = {}; // 返回 momoize 函数, 因为返回函数内部引用了 memoize.cache, 构成了闭包,变量保存在了内存中。 return memoize; };
质数为在大于 1 的天然数中,除了 1 和它自己之外再也不有其余因数。
咱们经过判断素数的函数,看看使用了函数记忆后的效果。app
function isPrime(value) { console.log("isPrime 被执行了!"); var prime = value != 1; // 1 不是素数,其余数字默认是素数。 for (var i = 2; i < value; i++) { if (value % i == 0) { prime = false; break; } } return prime } let momoizedIsPrime = memoize(isPrime); momoizedIsPrime(5) // isPrime 被执行了! momoizedIsPrime(5) // 第二次执行,没有打印日志!
斐波那契数列的特色是后一个数等于前面两个数的和指的是这样一个数列:一、一、二、三、五、八、1三、2一、……在数学上,斐波那契数列以以下被以递归的方法定义:F0=0,F1=1,Fn=Fn-1+Fn-2函数
计算斐波那契数列是用来演示函数记忆很好的例子,由于计算斐波那契数列函数里面用了大量的递归。性能
var count = 0; var fibonacci = function(n) { count++; return n < 2 ? n : fibonacci(n - 2) + fibonacci(n - 1); } for(var i = 0; i<= 10; i++) { console.log(`i: ${i}, ` + fibonacci(i)); } // i: 0, 0 // i: 1, 1 // i: 2, 1 // i: 3, 2 // i: 4, 3 // i: 5, 5 // i: 6, 8 // i: 7, 13 // i: 8, 21 // i: 9, 34 // i: 10, 55 console.log(count); // 453 !!!
咱们能够看出,若是从 0 开始打印斐波那契数列,fibonacci 函数被执行了 453 次。那咱们就牺牲一小部份内存,用来缓存每次计算的值。优化
fibonacci = memoize(fibonacci); for(var i = 0; i<= 10; i++) { console.log(`i: ${i}, ` + fibonacci(i)); } console.log(count); // 12
经过 memoize 函数记忆,使得函数执行次数只须要 12 次,大大优化了函数执行计算的性能消耗,this
函数记忆(memoization)将函数的参数和结果值,保存在对象当中,用一部分的内存开销来提升程序计算的性能,经常使用在递归和重复运算较多的场景。