递归是拖慢脚本运行速度的大敌之一。太多的递归会让浏览器变得越来越慢直到死掉或者莫名其妙的突然自动退出。
我们可以使用memoization技术来替代函数中太多的递归调用,通过封装一个Memoization函数去缓存之前运算结果,从而能避免无谓的运算避免重复工作,将执行过的运算或操作,缓存起来,如果后续有相同的操作可直接从缓存中查找,没有则进行递归,可大大减少递归的工作量,提高性能。
一、简单实现
通过 Memoization 的定义和原理,我们就可以初步实现 Memoization
function memoizer(fundamental, cache){
cache = cache || {}
var shell = function(arg){
if (! (arg in cache)){
cache[arg] = fundamental(shell, arg)
}
return cache[arg];
};
return shell;
}
函数缓存其实就是利用闭包,将函数参数作为存储对象的键(key),函数结果作为存储对象的 value 值。原有函数被设置为第一个参数,第二个参数是缓存对象,为可选参数,因为并不是所有的递归函数都包含初始信息。在函数内部,使用一个对象来缓存函数值,在shell函数里,我使用了in操作符来判断参数是否已经包含在缓存里。这种写法比测试类型不是undefined更加安全,因为undefined是一个有效的返回值。
二、使用场景 计算斐波那契数列
斐波那契数列的特点是后一个数等于前面两个数的和
指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波那契数列以如下被以递推的方法定义:F(0)=0,F(1)=1, F(n)=F(n - 1)+F(n - 2)(n ≥ 2,n ∈ N*)
var count = 0;
var fibonacci = function(n) {
count++;
return n < 2 ? n : fibonacci(n - 2) + fibonacci(n - 1);
}
fibonacci(40)
// 102334155
console.log(count);
//331,160,280 !!! 由于每有缓存结果,每次都需要进行函数调用
这里可以看到 在使用递归函数计算的时候,没有做结果缓存,每次计算都要重新调用函数计算。那我们就牺牲一小部分内存,用来缓存每次计算的值,就可以减少函数的调用
var count = 0
fibonacci =
memoizer(function (recur, n) {
count++
return recur(n - 1) + recur(n - 2);
}, {"0":0, "1":1});
fibonacci(40)
console.log(count);
// 39 可以看到最后通过 memoize 函数记忆,使得函数执行次数只需要39次,大大优化了函数执行计算的性能消耗,
三、总结
函数记忆(memoization)将函数的参数和结果值,保存在对象当中,用一部分的内存开销来提高程序计算的性能,常用在递归和重复运算较多的场景中,对于那些有着严格定义的结果集的递归算法来说,提升运行效果比较显著。
参考文献
https://humanwhocodes.com/blog/2009/01/20/speed-up-your-javascript-part-2/
https://humanwhocodes.com/blog/2009/01/20/speed-up-your-javascript-part-3/
网友评论