函數可以將先前操作的結果記錄在某個對象里,從而避免無謂的重復運算。這種優化方式被稱為記憶(memoization)。JavaScript的對象和數組要實現這種優化是非常方便的。
比如說,我們想要一個遞歸函數來計算Fibonacci數列。一個Fib數字是之前兩個Fib數字的和。最前面的兩個數字是0和1。
var count=0; var fib=function(n){ count++; return n<2?n:fib(n-1)+fib(n-2); }; for(var i=0;i<=10;i++){ console.log(fib(i)) } //0 //1 //1 //2 //3 //5 //8 //13 //21 //34 //55 count //453
這樣是可以工作的,但它做了很多無謂的工作,fib函數被調用了453次。如果我們讓該函數具備記憶功能,就可以顯著地減少運算量。
我們在一個名為memo的數組里保存我們的存儲結果,存儲結果可以隱藏在閉包中。當函數被調用時,這個函數首先檢查結果是否已經存在,如果已經存在,就立刻返回這個結果。
var count=0; var fibonacci=function(){ var memo=[0,1]; var fib=function(n){ count++; var result=memo[n]; if(typeof result!=='number'){ result=fib(n-1)+fib(n-2); memo[n]=result; } return result; }; return fib; }(); for(var i=0;i<=10;i++){ console.log(fibonacci(i)) } //結果同上 count //29
這個函數返回同樣的結果,但它只被調用了29次。
我們可以把這種技術推而廣之,編寫一個函數來幫助我們構造帶記憶功能的函數。memoizer函數取得一個初始的memo數組和formula函數。它返回一個管理memo存儲和在需要時調用formula函數的recur函數。我們把這個recur函數和它的參數傳遞給formula函數:
var memoizer=function(memo,formula){ var recur=function(n){ var result=memo[n]; if(typeof result!=='number'){ result=formula(recur,n); memo[n]=result; } return result; }; return recur; };
現在我們可以使用memoizer函數來定義fibonacci函數,提供其初始的memo數組和formula函數:
var fibonacci=memoizer([0,1],function(recur,n){ return recur(n-1)+recur(n-2); });
通過設計這種產生另一個函數的函數,極大的減少了我們的工作量。例如,要產生一個可記憶的階乘函數,我們只需提供基本的階乘公式即可:
var factorial=memoizer([1,1],function(recur,n){ return n*recur(n-1); });
