# 记忆函数
# 什么是记忆函数?
记忆函数是能够对长递归、长迭代进行性能优化的编程实践。其作用是作数据缓存,减少不必要的逻辑运算。
# 记忆函数使用在哪些场景?
当需要多次遍历去寻找一个重复项时,便可以使用记忆函数。以下例举几个场景。
- 搜索一个单词的释义,每次搜索出结果,都将单词名称和释义,以键值对方式存储到记忆函数中。当下次搜索时,将优先在记忆函数中寻找,如果找到对应词汇,则无需调用接口发送请求,直接返回其值。
- 有些需求中,地区列表是通过实时调用接口完成的,请求数据时将带有市区编号的payload发送出去,返回相关地段的信息。当下次请求时,携带的payload相同时,便可使用记忆函数,减少不必要的重复请求。
- 斐波那契数列。
# 记忆函数的原理
当一组参数初次参与到函数中,其参数和计算结果将被缓存下来,当再次传入相同的参数调用API时,则直接返还该参数所映射的值,即被缓存下来的计算结果。
注意
记忆函数不可以有副作用
# 记忆函数的实现
首先看一看《JavaScript权威指南》中是怎么实现的
function memorize(f) {
var cache = {}; // 定义缓存对象
console.log("cache",cache);
// 返回一个函数,即闭包
return function () {
// 将参数的长度 拼接 参数名字符串化(并以逗号隔开),作为键名,传入缓存对象
var key = arguments.length + Array.prototype.join.call(arguments, ",");
// 同cache.hasOwnProperty(key)
if (key in cache) {
return cache[key];
} else return (cache[key] = f.apply(this, arguments)); // 传入的数组或类数组,里面的元素将会被一一传入
};
}
# 函数测试
以一个计算参数和的函数为例,使用记忆函数时,执行时间在37毫秒左右。不使用记忆函数时,执行时间在0.7毫秒左右。在这里,总是接受相同参数。计算结果没有变化,且循环量大的情况下,记忆函数不太适用。
function sum(a, b, c) {
return a +b + c;
}
let sumMemorize = memorize(sum);
function calcTime(funcName, count) {
console.time(`${funcName}计时`);
for (let i = 0; i < count; i++) {
funcName == "sum" ? sum(1, 2, 3) : sumMemorize(1, 2, 3);
}
console.timeEnd(`${funcName}计时`);
}
calcTime("sum", sum, Math.pow(10,5)); // 平均0.077ms
// calcTime("sumMemorize", Math.pow(10,5)); // 平均36.465ms
再以斐波那契数列为例,使用记忆函数时,执行次数是5。不使用时,执行次数为19。在这里,总是接受不同参数,计算结果多变,且循环量大的情况下,记忆函数的优势便凸显出来。
// 斐波那契数列
var count = 0; // 斐波那契数列循环次数
// 如果n小于2,返回本身,反则返回前两项之和
function fibonacci(n) {
count++;
return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);
}
fibonacci = memorize(fibonacci);
for (let i = 0; i < 5; i++) {
fibonacci(i);
}
// console.log(count); // 19 --未使用记忆函数
console.log(count); // 5 -- 使用记忆函数
# 总结
- 记忆函数能够缓存计算结果,以参数作为映射。
- 记忆函数不能有副作用
- 记忆函数在参数多变,计算结果多变,且迭代次数多的情况下,才能显出优势。