在計算機領域,記憶(memoization)是主要用於加速程序計算的一種優化技術,它使得函數避免重複演算以前已被處理過的輸入,而返回已緩存的結果。 -- wikipediajavascript
Memoization
的原理就是把函數的每次執行結果都放入一個對象中,在接下來的執行中,在對象中查找是否已經有相應執行過的值,若是有,直接返回該值,沒有才真正執行函數體的求值部分。在對象裏找值是要比執行函數的速度要快的。java
另外,Memoization
只適用於肯定性算法,對於相同的輸入老是生成相同的輸出,即純函數。git
經過 Memoization 的定義和原理,咱們就能夠初步實現 Memoization 了。github
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 次,大大優化了函數執行計算的性能消耗。
函數記憶(memoization)將函數的參數和結果值,保存在對象當中,用一部分的內存開銷來提升程序計算的性能,經常使用在遞歸和重複運算較多的場景。