JavaScript Memoization:让函数也有记忆功能

简介:

realazy在blog上给出了一个JavaScript Memoization的实现,Memoization就是函数返回值的缓存,比如一个函数参数与返回结果一一对应的hash列表,wiki上其实也有详细解释,我不细说了,只讨论一下具体实现的问题,realazy文中的代码有一些问题,比如直接用参数拼接成的字符串作为查询缓存结果的key,如果参数里包括对象或数组的话,就很难保证唯一的key,还有1楼评论里提到的:[221,3]和[22,13]这样的参数也无法区分。

那么来改写一下,首先还是用hash表来存放缓存数据:


  1. function Memoize(fn){
  2.     var cache = {};
  3.     return function(){
  4.         var key = [];
  5.         for( var i=0l = arguments.lengthi < li++ ) 
  6.             key.push(arguments[i]);
  7.         if( !(key in cache) )
  8.             cache[key] = fn.apply(thisarguments);
  9.         return cache[key];
  10.     };
  11. }

嗯,区别是直接把数组当作键来用,不过要注意函数里的arguments是js解释器实现的一个特殊对象,并不是真正的数组,所以要转换一下……

ps: 原来的参数包括方法名称和上下文引用:fib.fib_memo = Memoize(‘fib_memo’, fib),但实际上currying生成的函数里可以用this直接引用上层对象,更复杂的例子可以参考John Resig的makeClass,所以我改成直接传函数引用:fib.fib_memo = Memoize(fib.fib_memo)

这样写看上去似乎很靠谱,由参数组成的数组不是唯一的么。但实际上,数组之所以能作为js对象的属性名称来使用,是因为它被当作字符串处理了,也就是说如果你给函数传的参数是这样:(1,2,3), cache对象就会是这个样子:{ “1,2,3″: somedata },如果你的参数里有对象,比如:(1,2,{i:”yy”}),实际的键值会是:”1,2,[object Object]“,所以这跟把数组拼接成字符串的方法其实没有区别……

示例:

  1. var a = [1,2,{yy:'0'}];
  2. var b = [1,2,{xx:'1'}];
  3. var obj = {};
  4. obj[a] = "111";
  5. obj[b] = "222";
  6. for( var i in obj )
  7.     alert( i + " = " + obj[i] );    //只会弹出"1,2,[object Object] = 222",obj[a] = "111"被覆盖了

直接用参数作为键名的方法不靠谱了…………换一种方法试试:

  1. function Memoize(fn){
  2.     var cache = {}args = [];
  3.     return function(){
  4.         for( var i=0key = args.lengthi < keyi++ ) {
  5.             if( equal( args[i]arguments )  )
  6.                 return cache[i];
  7.         }
  8.         args[key] = arguments;
  9.         cache[key] = fn.apply(thisarguments);
  10.         return cache[key];
  11.     };
  12. }

可以完全避免上述问题,没有使用hash的键值对索引,而是把函数的参数和结果分别缓存在两个列表里,每次都先遍历整个参数列表作比较,找出对应的键名/ID号之后再从结果列表里取数据。以下是比较数组的equal方法:

  1. function equal( firstsecond ){
  2.     if( !first || !second || first.constructor != second.constructor )
  3.         return false;
  4.     if( first.length && typeof first != "string"  )
  5.         for(var i=0l = ( first.length > second.length ) ? first.length : second.lengthi<li++){
  6.             if( !equal( first[i]second[i] ) ) return false;
  7.         }
  8.     else if( typeof first == 'object' )
  9.         for(var n in first){
  10.             if( !equal( first[n]second[n] ) ) return false;
  11.         }
  12.     else
  13.         return ( first === second );
  14.     return true;
  15. }

千万不要直接用==来比较arguments和args里的数组,那样比较的是内存引用,而不是参数的内容。

这种方法的速度很慢,equal方法其实影响不大,但是缓存的结果数量多了以后,每次都要遍历参数列表却是很没效率的(求80以上的fibonacci数列,在firefox3和safari3上都要40ms左右)

如果在实际应用中参数变动不多或者不接受参数的话,可以参考Oliver Steel的这篇《One-Line JavaScript Memoization》,用很短的函数式风格解决问题:

  1. function Memoize(op) {
  2.     var f = o[p]mfvalue;
  3.     var s = function(v) {return o[p]=v||mf};
  4.     ((mf = function() {
  5.         (s(function(){return value})).reset = mf.reset;
  6.         return value = f.apply(this,arguments)//此处修改过,允许接受参数
  7.     }).reset = s)();
  8. }

示例:

  1. var fib = {
  2.     tempfunction(n){
  3.         for(var i=0;i<10000;i++)
  4.             n=n+2;
  5.         return n;
  6.     }
  7. }
  8.  
  9. Memoize(fib,"temp")//让fib.temp缓存返回值
  10.  
  11. fib.temp(16)//执行结果:20006,被缓存
  12. fib.temp(20)//执行结果:20006
  13. fib.temp(10)//执行结果:20006
  14.  
  15. fib.temp.reset()//重置缓存
  16. fib.temp(10)//执行结果:20010
本文转自艾伦 Aaron博客园博客,原文链接:http://www.cnblogs.com/aaronjs/archive/2012/06/30/2570823.html,如需转载请自行联系原作者
相关文章
|
8天前
|
JavaScript 前端开发
js实现点击音频实现播放功能
js实现点击音频实现播放功能
|
8天前
|
前端开发 JavaScript
使用JavaScript实现复杂功能:构建一个自定义的拖拽功能
使用JavaScript实现复杂功能:构建一个自定义的拖拽功能
|
16天前
|
JavaScript
变量和函数提升(js的问题)
变量和函数提升(js的问题)
|
16天前
|
JavaScript
常见函数的4种类型(js的问题)
常见函数的4种类型(js的问题)
10 0
|
16天前
|
JavaScript
写一个函数将N组<>(包含开始和结束),进行组合,并输出组合结果 (js)
写一个函数将N组<>(包含开始和结束),进行组合,并输出组合结果 (js)
9 0
|
27天前
|
自然语言处理 JavaScript 网络架构
js开发:请解释什么是ES6的箭头函数,以及它与传统函数的区别。
ES6的箭头函数以`=&gt;`定义,简化了函数写法,具有简洁语法和词法作用域的`this`。它无`arguments`对象,不能用作构造函数,不支持`Generator`,且不改变`this`、`super`、`new.target`绑定。适用于简短表达式,常用于异步编程和高阶函数。
17 5
|
1天前
|
JavaScript 安全 前端开发
|
2天前
|
缓存 JavaScript 前端开发
js的入口函数,入口函数的作用
js的入口函数,入口函数的作用
12 4
|
7天前
|
JavaScript 前端开发
如何用JS实现选项卡功能
如何用JS实现选项卡功能
11 0
|
8天前
|
存储 前端开发 JavaScript
使用JavaScript实现复杂功能——一个交互式音乐播放器
使用JavaScript实现复杂功能——一个交互式音乐播放器