Memoization的定义:
memoization 一词是Donald Michie 根据拉丁语memorandum杜撰的一个词。相应的动词、过去分词、ing形式有memoiz、memoized、memoizing.
Memoization 是一种将函数返回值缓存起来的方法,Memoization 原理非常简单,就是把函数的每次执行结果都放入一个键值对(数组也可以,视情况而定)中,在接下来的执行中,在键值对中查找是否已经有相应执行过的值,如果有,直接返回该值,没有才 真正执行函数体的求值部分。很明显,找值,尤其是在键值对中找值,比执行函数快多了。现代 JavaScript 的开发也已经大量使用这种技术。
一个简单的使用memoization的例子
我们知道,在不同的浏览器中,xmlHttpRequest对象的具体实现都不同。需要判断何种浏览器以执行具体的方法。这里就有一个使用memoization来实现的例子。
function createXHRObject = function(){
//先把三个匿名函数缓存起来。
var methods = [
function(){return new XMLHttpRequest();},
function(){return new ActiveXObject("Msxml2.XMLHTTP");},
function(){return new ActiveXObject("Microsoft.XMLHTTP");}
];
for(var i=0,len=methods.length;i<len;i++){
try{//这里用try catch来代替了条件判断,通常我不赞成这种写法
methods[i]();
}
catch(e){
continue;//如果报异常,则执行下一次循环
}
// 把createXHRObject 与能正常执行的匿名函数对应起来,再调用createXHRObject不用再检测浏览器了
createXHRObject = method[i];
return method[i];
}
}
以上是一个简单的例子,第一次执行createXHRObject()的时候,会循环判断methods 中的方法,获取一个能正确执行的,并将createXHRObject的引用指向这个方法。以后再使用这个方法的时候,不用去判断,直接自动获取正确的方法。这省去了频繁的ajax调用中浏览器的检测。
当然,这个方法看上去效率的提升不是特别明显,我之所以写上来,是因为能比较清晰的理解memoization是如何实现的。在递归调用的时候,memoization的威力才能更好的显现。
一个递归的例子:
function fib(n) {
if (n < 2) {
return n;
}
return fib(n - 1) + fib(n - 2);
}
这是一个经典的斐波纳契序列,fib(20) 会把fib这个方法执行21891次,如果是fib(40),这会执行331160281次。
再看看如何使用memoization来实现,由于fib的参数就是一个自然数,这里用一个数组来缓存fib函数执行的结果。数组的下标用来标识参数,该小标对应的值用来表示函数执行的结果。
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-1];
}
return fib;
})();
将Function的原型扩展memoize 和unmemoize 方法,这样你可以对任何函数实现memoize和解除memoize,当然,这个方法要慎重,对一些不是频繁执行的函数,没必要缓存:
Function.prototype.memoize = function() {
var pad = {};
var self = this;
var obj = arguments.length > 0 ? arguments[i] : null;
var memoizedFn = function() {
// 把参数作为数组保存,作为键,把函数执行的结果作为值缓存起来
var args = [];
for (var i = 0; i < arguments.length; i++) {
args[i] = arguments[i];
}
if (!(args in pad)) {
pad[args] = self.apply(obj, arguments);
}
return pad[args];
}
memoizedFn.unmemoize = function() {
return self;
}
return memoizedFn;
}
Function.prototype.unmemoize = function() {
alert("Attempt to unmemoize an unmemoized function.");
return null;
}
使用方法:
fib.memoize();