讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 建議66:使用函數實現歷史記錄 >

建議66:使用函數實現歷史記錄

函數可以利用對像去記住先前操作的結果,從而能避免無謂的運算,這種優化稱為記憶。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('<br>'+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次,去計算可能已剛計算過的值。如果使該函數具備記憶功能,就可以顯著減少它的運算次數。

先使用一個臨時數組保存存儲結果,存儲結果可以隱藏在閉包中。當函數被調用時,先看是否已經知道存儲結果,如果已經知道,就立即返回這個存儲結果。


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;

});

for(var i=0;i<=10;i+=1){

document.writeln('<br>'+i+':'+fibonacci(i));

}


這個函數返回同樣的結果,但它只被調用了29次,其中循環調用了11次,它自身調用了18次,去取得之前存儲的結果。當然,可以把這種函數形式抽像化,以構造帶記憶功能的函數。memoizer函數將取得一個初始的memo數組和fundamental函數。memoizer函數返回一個管理memo存儲和在需要時調用fundamental函數的shell函數。memoizer函數傳遞這個shell函數和該函數的參數給fundamental函數。


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來定義fundamental函數,提供初始的memo數組和fundamental函數。


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);

});