函数能够将以前的操做结果缓存在某个对象中,当下次调用时,若是遇到相同的参数,就直接返回缓存中的数据,从而避免无谓的重复运算。这种优化被称做记忆。javascript
举个例子:java
function add(a, b) {
return a + b;
}
// 假设 memoize 能够实现函数记忆
let memoizeAdd = memoize(add);
memoizeAdd(1, 2) // 3
memoizeAdd(1, 2) // 相同的参数,第二次调用时,从缓存中取出数据,而非从新计算一次
复制代码
记忆只是一种编程技巧,本质上是牺牲算法的空间复杂度以换取更优的时间复杂度,在客户端 Javascript 中代码的执行时间复杂度每每成为瓶颈,所以在大多数状况下,这种牺牲空间换取时间的作法是很是可取的。git
好比说,咱们想要一个递归函数来计算 Fibonacci 数列。 一个 Fibonacci 数字是以前两个 Fibonacci 数字之和。 最前面的两个数组是 0 和 1。github
let count = 0; //用于记录函数调用次数
let fibonacci = function(n){
count ++ ; // 每次调用函数将 count + 1
return n < 2 ? n : fibonacci(n-1) + fibonacci(n - 2);
}
for(let i = 0; i < 10; i++){
console.log(`i=${i}:`,fibonacci(i))
}
console.log('总次数:',count)
// i=0:0
// i=1:1
// i=2:1
// i=3:2
// i=4:3
// i=5:5
// i=6:8
// i=7:13
// i=8:21
// i=9:34
// 总次数: 276
复制代码
上面的代码自己是没什么问题的,但它作了不少无谓的工做。咱们在 for 循环中共调用了 10 次 fibonacci 函数,但实际上 fibonacci 函数被调用了 276 次,它自身调用了 266 次去计算可能已被刚刚计算过的值。若是咱们让该函数具有记忆功能,就能够显著地减小运算量。算法
接下来咱们来思考如何实现一个通用函数( memoize )来帮助咱们构造带记忆功能的函数。编程
原理上很简单,只是将函数的参数和对应的结果一并缓存至闭包中,待调用时判断参数对应的数据是否存在,存在就直接返回缓存的数据结果。数组
代码实现以下:缓存
let memoize = function(fn){
let cache = {};
return function(...args){
let key = JSON.stringify(args);
if(!cache.hasOwnProperty(key)){
cache[key] = fn.apply(this,args);
}
return cache[key];
};
}
复制代码
上面的代码,咱们将函数的参数转换为JSON字符串后用做缓存的 key,这以基本可以保证每次函数调用可经过参数获取精确的 Key 值。闭包
但对于一些特殊的参数经过 JSON.stringify 转换后,并不能得到真实的 key 值,好比 undefined、NaN、Infinity、正则对象、函数等。app
考虑给 memoize 函数增长一个函数类型的参数 resolver ,用于将缓存的 key 的生成规则转交给用户。
实现以下:
let memoize = function(fn,resolver){
let cache = {};
return function(...args){
let key = typeof resolver === 'function' ? resolver.apply(this,args) :JSON.stringify(args);
if(!cache.hasOwnProperty(key)){
cache[key] = fn.apply(this,args);
}
return cache[key];
};
}
复制代码
依然使用 Fibonacci 的例子来验证一下咱们完成的 memoize 函数。
let count = 0; //用于记录函数调用次数
let fibonacci = function(n){
count ++ ; // 每次调用函数将 count + 1
return n < 2 ? n : fibonacci(n-1) + fibonacci(n - 2);
}
fibonacci = memoize(fibonacci);
for(let i = 0; i < 10; i++){
console.log(`i=${i}:`,fibonacci(i))
}
console.log('总次数:',count)
// i=0: 0
// i=1: 1
// i=2: 1
// i=3: 2
// i=4: 3
// i=5: 5
// i=6: 8
// i=7: 13
// i=8: 21
// i=9: 34
// 总次数: 10
复制代码
未使用 memoize 时,n 为 30 时 fibonacci 函数执行时间以下:
console.time('no memoize time');
fibonacci(30);
console.timeEnd('no memoize time');
// no memoize time: 10.919ms
复制代码
使用 memoize 时,n 为 30 时 fibonacci 函数执行时间以下:
fibonacci = memoize(fibonacci);
console.time('memoize time');
fibonacci(30);
console.timeEnd('memoize time');
// memoize time: 0.331ms
复制代码
两者时间比较,咱们能够很清晰的看到使用 memoize 函数后,函数的调用时间大幅下降。
预计每周1-2篇文章,持续更新,欢迎各位同窗点赞+关注
后续内容参见写做计划
写做不易,若是以为稍有收获,欢迎~点赞~关注~