在計算機領域,記憶(memoization)是主要用於加速程序計算的一種優化技術,它使得函數避免重複演算以前已被處理過的輸入,而返回已緩存的結果。 -- wikipedia
Memoization
的原理就是把函數的每次執行結果都放入一個對象中,在接下來的執行中,在對象中查找是否已經有相應執行過的值,若是有,直接返回該值,沒有才真正執行函數體的求值部分。在對象裏找值是要比執行函數的速度要快的。javascript
另外,Memoization
只適用於肯定性算法,對於相同的輸入老是生成相同的輸出,即純函數。java
經過 Memoization 的定義和原理,咱們就能夠初步實現 Memoization 了。算法
let memoize = function(func) { let cache = {}; return function(key) { if (!cache[key]) cache[key] = func.apply(this, arguments); return cache[key]; } }
是否是很簡單~ 函數記憶其實就是利用閉包,將函數參數做爲存儲對象的鍵(key),函數結果做爲存儲對象的 value 值。緩存
underscore 的源碼中有 Memoization 方法的封裝,它支持傳入一個 hasher 用來計算緩存對象 key 的計算方式。閉包
_.memoize = function(func, hasher) { var memoize = function(key) { // 把存儲對象的引用拿出來,便於後面代碼使用 var cache = memoize.cache; // hasher 是計算 key 值的方法函數。 // 若是傳入了 hasher,則用 hasher 函數來計算 key // 不然用 參數 key(即 memoize 方法傳入的第一個參數)當 key var address = '' + (hasher ? hasher.apply(this, arguments) : key); // 若是 key 尚未對應的 hash 值(意味着沒有緩存值,沒有計算過該輸入) // 就執行回調函數,並緩存結果值 if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); // 從緩存對象中取結果值 return cache[address]; }; // cache 對象被當作 key-value 鍵值對緩存中間運算結果 memoize.cache = {}; // 返回 momoize 函數, 因爲返回函數內部引用了 memoize.cache, 構成了閉包,變量保存在了內存中。 return memoize; };
質數爲在大於 1 的天然數中,除了 1 和它自己之外再也不有其餘因數。
咱們經過判斷素數的函數,看看使用了函數記憶後的效果。app
function isPrime(value) { console.log("isPrime 被執行了!"); var prime = value != 1; // 1 不是素數,其餘數字默認是素數。 for (var i = 2; i < value; i++) { if (value % i == 0) { prime = false; break; } } return prime } let momoizedIsPrime = memoize(isPrime); momoizedIsPrime(5) // isPrime 被執行了! momoizedIsPrime(5) // 第二次執行,沒有打印日誌!
斐波那契數列的特色是後一個數等於前面兩個數的和指的是這樣一個數列:一、一、二、三、五、八、1三、2一、……在數學上,斐波那契數列以以下被以遞歸的方法定義:F0=0,F1=1,Fn=Fn-1+Fn-2函數
計算斐波那契數列是用來演示函數記憶很好的例子,由於計算斐波那契數列函數裏面用了大量的遞歸。性能
var count = 0; var fibonacci = function(n) { count++; return n < 2 ? n : fibonacci(n - 2) + fibonacci(n - 1); } for(var i = 0; i<= 10; i++) { console.log(`i: ${i}, ` + fibonacci(i)); } // 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 // i: 10, 55 console.log(count); // 453 !!!
咱們能夠看出,若是從 0 開始打印斐波那契數列,fibonacci 函數被執行了 453 次。那咱們就犧牲一小部份內存,用來緩存每次計算的值。優化
fibonacci = memoize(fibonacci); for(var i = 0; i<= 10; i++) { console.log(`i: ${i}, ` + fibonacci(i)); } console.log(count); // 12
經過 memoize 函數記憶,使得函數執行次數只須要 12 次,大大優化了函數執行計算的性能消耗,this
函數記憶(memoization)將函數的參數和結果值,保存在對象當中,用一部分的內存開銷來提升程序計算的性能,經常使用在遞歸和重複運算較多的場景。