记忆化(memoization)是一种构建函数的处理过程,让函数能够记住计算的结果。简而言之,就是把第一次计算的结果按照传参保存到某个地方,当以后以相同参数调用时不需重复执行,直接返回上次的结果,从而避免重复而繁琐的计算,提高了函数的执行性能。
那么,到底把这些值保存到哪里呢?根据Javascript语法,函数是第一类对象,我们可以自由地给函数定义一些属性,于是很自然地可以想到,我们可以给函数定义一个属性以保存这些缓存的键值,下面是一个示例:
function square (a) {
square.mem || (square.mem = {});
var mitem = square.mem[a];
if (typeof mitem !== "undefined") {
console.log('get result from cache');
return mitem;
} else {
console.log('get result by calculation');
return (square.mem[a] = a * a);
}
}
square(33) // get result by calculation 1089
square(33) // get result from cache 1089
square(33) // get result from cache 1089
从打印结果可知,第二次和第三次执行square
函数,直接是从square.mem
中拿数据,而不是再进行一次运算,我们的目的达到了。
但是这样做会存在一些问题,如果我把square.mem
干掉,则所有已被缓存的键要被重新计算一次:
square.mem = undefined;
square(33) // get result by calculation 1089
square(33) // get result from cache 1089
而且如果人为地对square.mem
进行一些赋值操作,在随后的函数调用时会得到错误的结果:
square.mem[33] = 233;
square(33) // get result from cache 233
考虑到这些封装性问题,我们可以使用闭包来解决:
var getElement = (function (){
var mem = {};
return function (selector) {
var mitem = mem[selector];
if (mitem) {
console.log('get element from cache');
return mitem
} else {
console.log('get element by dom api');
return (mem[selector] = document.querySelector(selector));
}
}
})()
getElement
作为一个IIFE
的执行结果,函数体内调用了上层匿名函数的mem
变量,形成一个闭包。mem
存在于封闭的匿名函数作用域内,不能被外部拿到,从而提高了封装性。
进一步,把这个功能封装成一个可复用的函数,也是一个常用的技巧:
function cached (fn) {
const cache = Object.create(null);
return (function cachedFn (str) {
const hit = cache[str];
return hit || (cache[str] = fn(str));
})
}
var getElement = cached(selector => document.querySelector(selector));
另外,我们也可以利用闭包的存储功能,实现单例模式:
class Hole{
self_url = './hole';
self_goes = 'dooes';
}
var getUniqueHole = (function () {
var hole = null;
return function () {
if (hole) {
return hole
} else {
return (hole = new Hole);
}
}
})()
var a = getUniqueHole() // Hole {self_url: "./hole", self_goes: "dooes"}
var b = getUniqueHole() // Hole {self_url: "./hole", self_goes: "dooes"}
console.log(a === b) // true
第一次执行getUniqueHole
会把创建的单例对象hole
放在闭包内,下次调用时拿到的其实就是它,所以对两次调用,得到的是相同的引用。
根据以上分析,函数记忆可以优化执行效率,带来运行速度上的提升,但因计算值需要被缓存,必定需要额外的内存空间。对于小而简单的函数,使用这种方法可能适得其反。另外,只有执行多次和执行一次结果相同的函数才可以使用记忆化。