天天看點

JS程式設計建議——65:比較函數的惰性求值與非惰性求值

建議65:比較函數的惰性求值與非惰性求值

在JavaScript中,使用函數式風格程式設計時,應該對于表達式有着深刻的了解,并能夠主動使用表達式的連續運算來組織代碼。

1)在運算元中,除了JavaScript預設的資料類型外,函數也作為一個重要的運算元參與運算。

2)在運算符中,除了JavaScript的大量預定義運算符外,函數還作為一個重要的運算符進行計算群組織代碼。

函數作為運算符參與運算,具有非惰性求值特性。非惰性求值行為自然會對整個程式産生一定的負面影響。先看下面這個示例:

var a = 2;

function f(x){

}

alert(f(a,a=a*a)); //2

alert(f(a)); //4

在上面的示例中,兩次調用同一個函數并傳遞同一個變量,所傳回的值卻不一樣。在第一次調用函數時,向其傳遞了兩個參數,第二個參數是一個表達式,該表達式對變量a進行重新計算和指派。也就是說,當調用函數時,第二個參數雖然不使用,但是也被計算了。這就是JavaScript的非惰性求值特性,也就是說,不管表達式是否被利用,隻要在執行代碼行中都會被計算。

如果在一個函數參數中無意添加了幾個表達式,雖然這樣不會對函數的運算結果産生影響,但是由于表達式被執行,就會對整個程式産生潛在的負面影響。

在惰性求值語言中,如果參數不被調用,那麼無論參數是直接量還是某個表達式,都不會占用系統資源。但是,由于JavaScript支援非惰性求值,問題就變得很特殊了。

function f(){}

f( function(){while(true);}())

在上面的示例中,雖然函數f沒有參數,但是在調用時将會執行傳遞給它的參數表達式,該表達式是一個死循環結構的函數值,最終将導緻系統崩潰。

惰性函數模式是一種将對函數或請求的處理延遲到真正需要結果時進行的通用概念,很多應用程式都采用了這種概念。從惰性程式設計的角度來思考問題,可以幫助消除代碼中不必要的計算。例如,在Scheme語言中,delay特殊表單接收一個代碼塊,它不會立即執行這個代碼塊,而是将代碼和參數作為一個promise存儲起來。如果需要promise産生一個值,就會運作這段代碼。promise 随後會儲存結果,這樣将來再請求這個值時,該值就可以立即傳回,而不用再次執行代碼。這種設計模式在JavaScript中大有用處,尤其是在編寫跨浏覽器的、高效運作的庫時非常有用。例如,下面是一個時間對象執行個體化的函數。

var t;

function f(){

f(); // 調用函數

上面的示例使用全局變量t來存儲時間對象,這樣在每次調用函數時都必須進行重新求值,代碼的效率沒有得到優化,同時全局變量t很容易被所有代碼通路和操作,存在安全隐患。當然,可以使用閉包隐藏全局變量t,隻允許在函數f内通路。

var f =(function(){

})();

f();

這仍然沒有提高調用時的效率,因為每次調用f依然需要求值:

var f = function() {

};

在上面的示例中,函數f的首次調用将執行個體化一個新的Date對象并重置f到一個新的函數上,f在其閉包内包含Date對象。在首次調用結束之前,f的新函數值也已被調用并提供傳回值。

函數f的調用都隻會簡單地傳回t保留在其閉包内的值,這樣執行起來非常高效。弄清這種模式的另一種途徑是,外部函數f的首次調用是一個保證(promise),它保證了首次調用會重定義f為一個非常有用的函數,保證來自于Scheme的惰性求值機制。

繼續閱讀