函數可以用對象去記住先前操作的結果,從而能避免無謂的運算,這種最佳化被稱為記憶(Memoization)。JavaScript 的對象和數組要實現這種最佳化是非常方便的
Memoization 是一種將函數傳回值緩衝起來的方法,在 Lisp, Ruby, Perl, Python 等語言中使用非常廣泛。隨著 Ajax 的興起,用戶端對伺服器的請求越來越密集(經典如 autocomplete),如果有一個良好的緩衝機制,那麼用戶端 JavaScript 程式的效率的提升是顯而易見的。
Memoization 原理非常簡單,就是把函數的每次執行結果都放入一個散列表中,在接下來的執行中,在散列表中尋找是否已經有相應執行過的值,如果有,直接返回該值,沒有才真正執行函數體的求值部分。很明顯,找值,尤其是在散列中找值,比執行函數快多了。現代 JavaScript 的開發也已經大量使用這種技術。
比如說,我們想要一個遞迴函式來計算 Fibonacci 數列。一個 Fibonacci 數字是之前兩個 Fibonacci 數字之和。最前面的兩個數字是 0 和 1。
var fibonacci = function (n) { return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2);};for (var i = 0; i <= 10; i += 1) { document.writeln('// ' + i + ': ' + fibonacci(i));}// 0: 0// 1: 1// 2: 1// 3: 2// 4: 3// 5: 5// 6: 8// 7: 13// 8: 21// 9: 34// 10: 55
這樣是可以工作的,但是它做了很多無謂的工作。 Fibonacci 函數被調用了 453 次。我們調用了 11 次,而它自身調用了 442 次去計算可能已經被剛計算過的值。如果我們讓該函數具備記憶功能,就可以顯著地減少它的運算量。
我們在一個名為 memo 的數組裡儲存我們的儲存結果,儲存結果可以隱藏在閉包中。當我們的函數被調用時,這個函數首先看是否已經知道計算的結果,如果已經知道,就立即返回這個儲存結果。
var fibonacci = function() { var memo = [0, 1]; var fib = function (n) { var result = memo[n]; if (typeof result !== 'number') { result = fib(n - 1) + fib(n - 2); memo[n] = result; } return result; }; return fib;}();
這個函數返回同樣的結果,但是它只被調用了 29 次。我們調用了它 11 次,它自身調用了 18 次去取得之前儲存的結果。