memoization是一种非常有用的优化技术,它缓存特定输入产生的相应结果。这样麻烦的查找和迭代计算可以尽可能的减少。
它基本的思想是针对特定的输入,已经计算过的结果都是通过缓存当中的数据直接返回而不是经过重复的计算。
实现记忆函数
我们可以简单的将memoization理解为记忆函数经过特定输入产生的结果。
下面是一种基本简单且实用的实现方法,可以很清晰的显示记忆函数的结构
function memoize( fn ) { return function () { // 将参数转为数组 var args = Array.prototype.slice.call(arguments); // 创建缓存对象在目标函数对象里 fn.cache = fn.cache || {}; // 如果缓存当中有相应的结果直接从缓存中返回,否则重新计算并存储缓存对象 return args in fn.cache ? fn.cache[args] : fn.cache[args] = fn.apply(this, args); }; }
为了方便我们可以更形象的理解记忆函数,我们将上面的三目运算符改成if语句,并添加log。
function memoize( fn ) { return function () { // 将参数转为数组 var args = Array.prototype.slice.call(arguments); // 创建缓存对象在目标函数对象里 fn.cache = fn.cache || {}; // 如果缓存当中有相应的结果直接从缓存中返回,否则重新计算并存储缓存对象 var results; if (args in fn.cache) { console.log('返回结果来自于缓存:'); results = fn.cache[args]; }else { console.log('返回结果来自于函数计算:'); results = fn.cache[args] = fn.apply(this, args); } return results }; }
实际使用
这里我们将其运用到实际中去,下面是一个计算最大公约数的函数。但它不是学习的要点,我们只要知道它接收两个数字,并返回他们的最大公约数。
// 这是一个计算最大公约数的函数 function gcd(a ,b){ var t; if (a < b) t = b, b = a, a = t; while(b != 0) t=b, b= a%b, a=t; return a; } var cachedGcd = memoize(gcd); console.log(cachedGcd(85, 187)); // 返回结果来自于函数计算: 17 console.log(cachedGcd(85, 187)); // 返回结果来自于缓存:
在上面这段代码,可以看到经memoize处理过的gcd函数便具备记忆缓存的作用。不只是gcd,对于任何特定输入产生特定结果的函数都可以使用记忆函数。
简单测试
使用time简单的测试一下非记忆时,和记忆时函数调用的时间。在不同的浏览器有不同的结果,但是,整体来看记忆时的时间是比非记忆时要短的。
console.time('non-memoized')
cachedGcd(85,187);
console.timeEnd('non-memoized')
console.time('memoized')
cachedGcd(85,187);
console.timeEnd('memoized')
由于gcd函数涉及的运算相对比较少,这还不能很明显的体现记忆函数的优势,试想一下,如果一个函数运算量很大很大。那么通过缓存返回的结果将直接跳过这些复杂的运算,迅速返回结果。