函數能夠將以前的操做結果緩存在某個對象中,當下次調用時,若是遇到相同的參數,就直接返回緩存中的數據,從而避免無謂的重複運算。這種優化被稱做記憶。javascript
函數能夠將以前的操做結果緩存在某個對象中,當下次調用時,若是遇到相同的參數,就直接返回緩存中的數據,從而避免無謂的重複運算。這種優化被稱做記憶。java
舉個例子:算法
function add(a, b) {
return a + b;
}
// 假設 memoize 能夠實現函數記憶
let memoizeAdd = memoize(add);
memoizeAdd(1, 2) // 3
memoizeAdd(1, 2) // 相同的參數,第二次調用時,從緩存中取出數據,而非從新計算一次
複製代碼
記憶只是一種編程技巧,本質上是犧牲算法的空間複雜度以換取更優的時間複雜度,在客戶端 Javascript 中代碼的執行時間複雜度每每成爲瓶頸,所以在大多數狀況下,這種犧牲空間換取時間的作法是很是可取的。編程
好比說,咱們想要一個遞歸函數來計算 Fibonacci 數列。 一個 Fibonacci 數字是以前兩個 Fibonacci 數字之和。 最前面的兩個數組是 0 和 1。數組
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 )來幫助咱們構造帶記憶功能的函數。閉包
原理上很簡單,只是將函數的參數和對應的結果一併緩存至閉包中,待調用時判斷參數對應的數據是否存在,存在就直接返回緩存的數據結果。app
代碼實現以下:函數
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、正則對象、函數等。
考慮給 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 函數後,函數的調用時間大幅下降。
喜歡嗎?別忘了給聲先生點個贊👍