Memoization,简单的说就是优化计算机的性能,缓存那些重复性的函数操作和计算,使得第一次之后的调用可以直接从缓存中得到结果,而无需重新计算和运行复杂、费时的函数。这个跟Lazy Definition的原理是比较相似的,Lazy Definition主要是对函数进行重复定义,避免浏览器检测等恶心的事情。在javascript里实现Memoization的技术,Keith Gaughan做了相关的叙述:Memoization in JavaScript。特别是它对Fibonacci的优化让我特别的玩味。
对于Fibonacci普通的实现方式是:
//这个性能不咋的,函数调用太频繁了
function Fib(n) {
if (n < 2) { return n;}
return Fib(n - 1) + Fib(n - 2);
}
而Keith Gaughan对它的实现方式用Memoization进行了优化:
var IterMemoFib = function() {
var cache = [1, 1];
var fib = function(n) {
if (n >= cache.length) {
for (var i = cache.length; i <= n; i++) {
//这句代码很耐人寻味,还有循环的方式
cache[i] = cache[i - 2] + cache[i - 1];
}
}
return cache[n];
}
return fib;
}();
上面关于Fibonacci的优化,就是使用了Memoization技巧,将上一步的加法操作缓存起来,用户循环中下一轮的递增。缓存计算结果是Memorize应用最多的需求,避免了对计算结果的重复性计算,但是如果计算结果是动态的呢,或者说你想获取动态修改过后的计算结果呢? 比如DOM的length属性,这个是易变的,这个时候就不能去获取缓存的结果了,需要重复计算。
下面先来看一段Flagrant Badassery编写的一个Timed Memoization函数,一方面用于缓存结果,另一方面则是在时间参数指定的时间间隔里清除掉缓存的结果:
var memorize=function(fn,timer){
var obj = {};
return function(){
var key = [].join.call(arguments,"$");
if(timer){
setTimeout(function(){delete obj[key];},timer);
}
//这个返回方式本人进行了一点修改
return (key in obj) ? obj[key] : obj[key] = fn.apply(this,arguments);
}
}
上面的代码思路不错,在指定的时间之后清楚缓存结果,但是我觉得这个思路还可以用于更多场合,比如清除对象,及时断开作用域链,使得无用的对象可以及时的回收占用的内存。基于这个思路,本人做了些修修补补,编写了下面的几个方法:
//将memorize函数生成的函数也缓存起来
var memorize2 = function(fn,timer){
var key = [].join.call(arguments,"$"),obj={};
return memorize2[key] ? memorize2[key] : (memorize2[key] = function(){
var k = [].join.call(arguments,"$");
if(timer){
setTimeout(function(){delete obj[k];delete memorize2[key];},timer);
}
return obj[k] ? obj[k] : (obj[k] = fn.apply(this,arguments);
}
}
对于上面说到的,如果有些计算结果是会动态改变的,这时候就该在需要的时候进行重复计算,为此,编写了一个Function扩展,借鉴上面Timed Momeization的思路:
Function.prototype.memorize = function(timer){
var obj = {}, that = this, key = "$";
return function(flag){
var args = [].slice.call(arguments,1) || [];
if(flag){
return obj[key] = that.apply(this,args);
}
key = [].join.call(arguments,"$");
if(timer){
setTimeout(function(){delete obj[key];},timer);
}
return obj[key] ? obj[key] : obj[key] = that.apply(this,args);
}
}
如上面代码所示,使用memorize返回的函数中带有一个flag参数,如果不存在这个参数或者设置为false,则从缓存中去获取上一步缓存的结果,如果设置为true,则重复计算、并重新设置缓存的值。通过这样的实现方式,既可以通过参数的形式来判断获取动态改变的结果,也可以在不需要动态改变参数的时候获取缓存的结果,《测试用例》。使用方式如下:
var length = function(){
return div.getElementsByTagName("p").length;
}.memorize();
为此,Memoization对于一些静态的计算结果进行缓存,从而起到优化代码性能的作用;但是对于会动态改变的计算结果,则需要另当别论了。
更多的参考文章:《ONE-LINE JAVASCRIPT MEMOIZATION》,《Memoization in JavaScript》,《Timed Memoization》,《Javascript的Lazy Definition Pattern》
博主,你的空间是哪里的呀,速度很快呀
很喜欢你写的文章,我可以转载吗?
好文章,喜欢!希望继续有这种深度的文章,支持一下