JavaScript Memoization
Memoization 是一种将函数返回值缓存起来的方法,在 Lisp, Ruby, Perl, Python 等语言中使用非常广泛。随着 Ajax 的兴起,客户端对服务器的请求越来越密集(经典如 autocomplete),如果有一个良好的缓存机制,那么客户端 JavaScript 程序的效率的提升是显而易见的。
Memoization 原理非常简单,就是把函数的每次执行结果都放入一个散列表中,在接下来的执行中,在散列表中查找是否已经有相应执行过的值,如果有,直接返回该值,没有才真正执行函数体的求值部分。很明显,找值,尤其是在散列中找值,比执行函数快多了。现代 JavaScript 的开发也已经大量使用这种技术。
我通过 Google 寻找了好几种 JavaScript Memoization 的实现,都不太如人愿,有的实现不能缓存递归函数,有的需要修改函数的 prototype,于是自己实现一个:
/**
* JavaScript Momoization
* @param {string} func name of function / method
* @param {object} [obj] mothed’s object or scope correction object
*
* MIT / BSD license
*/
function Memoize(func, obj){
var obj = obj || window,
func = obj[func],
cache = {};
return function(){
var key = Array.prototype.join.call(arguments, "");
var key = Array.prototype.join.call(arguments, "_")
if (!(key in cache))
cache[key] = func.apply(obj, arguments);
return cache[key];
}
}
并写了一个测试案例,空口无凭,让大家亲自看看 Memoization 的威力。
见:http://realazy.org/lab/memoization.html
另,例子中的 fibonacci 函数有很多更有效率的实现方法,在此我使用最无效率的递归实现,只是为了更直达地演示 memoization.
又,longwosion 留言所提到的 key 唯一性问题,我略作修正,但应该还有更好的办法,欢迎您留言探讨。
多参数时,用那样的方式形成key的话不能保证唯一性。比如,我们要写一个下面的组合函数的话。
var comm = {
comm: function(m, n) {
if(n==0) return 1;
if(m==n) return 1;
if(n==1) return m;
return this.comm(m-1,n) + this.comm(m-1,n-1);
},
comm_memo: function(m, n) {
if(n==0) return 1;
if(m==n) return 1;
if(n==1) return m;
return this.comm_memo(m-1,n) + this.comm_memo(m-1,n-1);
}
}
comm.comm_memo = Memoize(’comm_memo’, comm);
下面两组参数会获得一样的结果,显然是不对的。
[221,3]
[22,13]
求 fib(n)? 乱吵一下, 当个小总结.
1 应该使用尾递归, 只是加一个参数…
2 应该使用矩阵乘法, 可以在 O(logn) 的时间内…
or 用这个结论
fib(2n) = fib(n-1)*fib(n) + fib(n)*fib(n+1)
fib(2n+1) = fib(n)^2 + fib(n+1)^2
fib(2n-1) = fib(n-1)^2 + fib(n)^2
—
ps: 楼上这位的程序, 也有相当大的优化空间.
@longwosion - 你的建议不错。
另外,不递归的话不能够提升效率吧,如果cache能够成为obj的私有属性或许不递归也能提升效率。
>>随着 Ajax 的兴起,客户端对服务器的请求越来越密集(经典如 autocomplete),如果有一个良好的缓存机制,那么客户端 JavaScript 程序的效率的提升是显而易见的。
既然需求是解决服务器请求过多的问题,那么直接缓存 Ajax 请求(URL + get/post 的数据)的结果应该可以解决问题,直接缓存函数貌似太复杂了。
帅! 前些日子还在想, 怎么能在前端做个很好的cache。 发现一些开源框架会缓存住selector取得的元素信息, 以为自己能做的只是尽可能缓存取来的数据。 这个利用闭包缓存住函数执行结果的方法提供了新的思路, 很赞:)
现在的前端开发对于程序的效率越来越高也越来越体系化了。。。
不错,学习中。。
@魏中华
或许我没说得太明白。我的意思是,在自动完成的输入过程中,在客户端缓存已经查找过的结果,在没有离开页面时,再次查找相同的值时,直接使用缓存结果了,并不用重新请求(即使请求已经缓存),而且也节省了函数的运行时间。
学习了。之前有关于“兼容性记忆体”的想法:就是浏览器在第一次执行某兼容代码时,记忆此时选择执行的分支,以确认当前浏览器支持的对象/方法,在以后的执行过程中,直接使用记忆体里的对象/方法。部分示例如下:
XmlHttpRequest.AXO = [
'MSXML3.XMLHTTP.5.0',
'MSXML3.XMLHTTP.4.0',
'MSXML3.XMLHTTP.3.0',
'MSXML3.XMLHTTP.2.0',
"Msxml3.XMLHTTP",
"Msxml2.XMLHTTP.5.0",
"Msxml2.XMLHTTP.4.0",
"Msxml2.XMLHTTP.3.0",
"Msxml2.XMLHTTP",
"Microsoft.XMLHTTP"];
XmlHttpRequest.AXOI=0; // 兼容性记忆体。
…
if(window.ActiveXObject){ // 支持ActiveX的(ie)
for (var i=XmlHttpRequest.AXOI, l=XmlHttpRequest.AXO.length; i
提个建议,要是Memoize函数能够接受 Refresh参数就更好了,
毕竟实际应用中有需要刷新cache 的需求。
btw,仰慕博主的威名,希望和博主有更多交流,方便请加我MSN:dylanjia@hotmail.com
期盼!
refresh希望渺茫。
另,参数唯一性:参数本身使用逗号分隔,何不将就。
var key = Array.prototype.join.call(arguments);
不应该把参数拼成字符串作key,如果参数里有object或数组之类的就败了哑,不如改成这样:
function Memoize(fn){
var cache = {}, args = [];
return function(){
for( var i=0, key = args.length; i second.length ) ? first.length : second.length; i
…评论有字数限制么…
我贴到BLOG上了:http://www.limboy.com/2008/04/27/javascript_memoization/
函数中包含global对象 就不能用这个函数了
你的文章回复系统 怎么设置的
我的回复http://hi.baidu.com/emkiao/blog/item/6612caedb8e0c3d1b21cb124.html
这东西似乎只能用在一个函数上。绑两个以上的函数就冲突了,想法很好但还要再通用些吧。