如同前面所提到的,记忆化(memoization)是一种构建函数的处理过程,能够记住上次计算结果。在这个果壳里,当函数计算得到结果时就将该结果按照参数存储起来。采用这种方式时,如果另一个调用也使用相同的参数,我们则可以直接返回上次存储的结果而不是再计算一遍。像这样避免既重复又复杂的计算可以显著地提高性能。对于动画中的计算、搜索不经常变化的数据或任何耗时的数学计算来说,记忆化这种方式是十分有用的。
看看下面的例子,它使用了一个简单的(也的确是效率不高的)算法来计算素数。尽管这是一个复杂计算的简单例子,但它经常被应用到大计算量的场景中(例如可以引申到通过字符串生成MD5算法)。
从外表来说,这个函数和任何普通函数一样,但在内部我们会构建一个结果缓存,它会保存函数每次计算得到的结果。
质数(prime number)又称素数,有无限个。质数定义为在大于1的自然数中,除了1和它本身以外不再有其他因数。
console.log('------------计算先得到的值----------------');
function isPrime(value) {
//创建缓存
if (!isPrime.answers) {
isPrime.answers = {}
}
//检查缓存的值
if (isPrime.answers[value] !== undefined) {
return isPrime.answers[value];
}
var prime = value !== 0 && value !== 1;//1 is not a prime
for (var i = 2; i < value; i++) {
if (value % i === 0) {
prime = false;
break;
}
}
return isPrime.answers[value] = prime;//存储计算的值
}
//测试该函数是否正常工作
if (isPrime(5)) {
console.log('5 is prime.');
}
if (isPrime.answers[5]) {
console.log('The answer was cached!');
}
在isPrime函数中,首先通过检查它的answers属性来确认是否已经创建了一个缓存,如果没有创建,则新建一个:
//创建缓存
if (!isPrime.answers) {
isPrime.answers = {}
}
只有第一次函数调用才会创建这个初始空对象,之后这个缓存就已经存在了。然后我们会检查参数中传值是否已经存储在缓存中:
//检查缓存的值
if (isPrime.answers[value] !== undefined) {
return isPrime.answers[value];
}
这个缓存会针对参数中的值value来存储该值是否为素数(true或false)。如果我们在缓存中找到该值,函数会直接返回。
return isPrime.answers[value] = prime;//存储计算的值
这个缓存是函数自身的一个属性,所以只要该函数还存在,缓存也就存在。
最后的测试结果可以看到记忆函数生效了。
//测试该函数是否正常工作
if (isPrime(5)) {
console.log('5 is prime.');
}
if (isPrime.answers[5]) {
console.log('The answer was cached!');
}
这个方法具有两个优点:
1.由于函数调用时会寻找之前调用所得到的值,所以用户最终会乐于看到所获得的性能收益。
2.它几乎是无缝地发生在后台,最终用户和页面作者都不需要执行任何特殊请求,也不需要做任何额外的初始化,就能顺利进行工作。
当然这种方法并完美,还要权衡利弊
1.任何类型的缓存都必然会牺牲内存。
2.纯粹主义者会认为缓存逻辑不应该和业务逻辑混合,函数或方法只需要把一件事做好。
3.对于这类问题,很难做负载测试或估计算法复杂度,因为结果依赖于函数之前的输入。
参考《JavaScript忍者秘籍》