讀古今文學網 > 編寫高質量代碼:改善JavaScript程序的188個建議 > 第3章 函數式編程 >

第3章 函數式編程

JavaScript是一門優美的語言,具有動態性、弱類型,具有C和LISP的雙重語法。JavaScript雖然是基於對像編程,但是對像不是第一型的,而函數是第一型的。

函數式編程思想的源頭可以追溯到20世紀30年代阿朗佐·丘奇進行的一項關於問題的可計算性的研究,也就是後來的lambda演算。lambda演算的本質就是一切皆函數,函數可以作為另外一個函數的輸出或輸入,一系列的函數使用最終會形成一個表達式鏈,通過這個表達式鏈可以最終求得一個值,而這個過程即為計算的本質。在函數式編程中,會發現代碼中存在大量的連續運算。

函數式編程已經在實際應用中發揮了巨大作用,更有越來越多的語言不斷地加入對諸如閉包、匿名函數等的支持,從某種程度上來講,函數式編程正在逐步同化命令式編程。

建議57:禁用Function構造函數

定義函數的方法包括3種:function語句、Function構造函數和函數直接量。不管用哪種方法定義函數,它們都是Function對象的實例,並將繼承Function對像所有默認或自定義的方法和屬性。


//使用function語句編寫函數

function f(x){

return x;

}

//使用Function構造函數克隆函數

var f=new Function(\"x\",\"return x;\");

//使用函數直接量直接生成函數

var f=function(x){

return x;

}


雖然這些方法定義函數的結構體相同,函數的效果相近,但是也存在很多差異,詳細比較見表3.1。

(1)作用域比較

使用Function構造函數創建的函數具有頂級作用域,JavaScript解釋器也總是把它作為頂級函數來編譯,而function語句和函數直接量定義的函數都有自己的作用域(即局部作用域,或稱為函數作用域)。例如:


var n=1;

function f{

var n=2;

function e{

return n;

}

return e;

}

alert(f);//2


在上面示例中,分別在函數體外和函數體內聲明並初始化變量n,然後在函數體內使用function語句定義一個函數e,定義該函數返回變量n的值。最後在函數體外調用函數的返回函數。通過結果可以發現,返回值為局部變量n的值(即為2),也就是說,function語句定義的函數擁有自己的作用域。同理,如果使用函數直接量定義函數e,當調用該返回函數時,返回值是2,而不是1,那麼也說明函數直接量定義的函數擁有自己的作用域。

但是,如果使用Function構造函數定義函數e,則調用該返回函數時,返回的值是1,而不再是2了,看來Function構造函數定義的函數作用域需要動態確定,而不是在定義函數時確定的,代碼如下:


var n=1;

function f{

var n=2;

var e=new Function(\"return n;\");

return e;

}

alert(f);//1


(2)解析效率比較

JavaScript解釋器在解析代碼時,並非一行行地分析和執行程序,而是一段段地分析執行。在同一段代碼中,使用function語句和函數直接量定義的函數結構總會被提取出來優先執行。只有在函數被解析和執行完畢之後,才會按順序執行其他代碼行。但是使用Function構造函數定義的函數並非提前運行,而是在運行時動態地被執行,這也是Function構造函數定義的函數具有頂級作用域的根本原因。

從時間角度審視,function語句和函數直接量定義的函數具有靜態的特性,而Function構造函數定義的函數具有動態的特性。這種解析機制的不同,必然帶來不同的執行效率,這種差異性可以通過將一個循環結構放大來比較得出。

在下面這個示例中,分別把function語句定義的空函數和Function構造函數定義的空函數放在一個循環體內,讓它們空轉十萬次,這樣就會明顯感到使用function語句定義的空函數運行效率更高。


//測試function語句定義的空函數執行效率

var a=new Date;

var x=a.getTime;

for(var i=0;i<100000;i++){

function{//使用function語句定義的空函數

;

}

}

var b=new Date;

var y=b.getTime;

alert(y-x);//62,不同環境和瀏覽器會存在差異

//測試Function構造函數定義的空函數執行效率

var a=new Date;

var x=a.getTime;

for(var i=0;i<100000;i++){

new Function;//使用Function構造函數定義的空函數

}

var b=new Date;

var y=b.getTime;

alert(y-x);//2390


JavaScript解釋器首先把function語句定義的函數提取出來進行編譯,這樣每次循環執行該函數時,就不再從頭開始重新編譯該函數對象了,而Function構造函數定義的函數每次循環時都需要動態編譯一次,這樣效率就非常低了。

(3)兼容性比較

從兼容角度考慮,使用function語句定義函數不用考慮JavaScript版本問題,所有版本都支持這種方法。而Function構造函數只能在JavaScript 1.1及其以上版本中使用,函數直接量僅在JavaScript 1.2及其以上版本中有效。當然,版本問題現在已經不是問題了。

Function構造函數和函數直接量定義函數方法有點相似,它們都是使用表達式來創建的,而不是通過語句創建的,這樣帶來了很大的靈活性。對於僅使用一次的函數,非常適合使用表達式的方法來創建。

由於Function構造函數和函數直接量定義函數不需要額外的變量,它們直接在表達式中參與運算,所以節省了資源,克服了使用function語句定義函數佔用內存的弊端,也就是說,這些函數運行完畢即被釋放而不再佔用內存空間。

對於Function構造函數來說,由於定義函數的主體必須以字符串的形式來表示,使用這種方法定義複雜的函數就顯得有點笨拙,很容易出現語法錯誤。但函數直接量的主體使用標準的JavaScript語法,這樣看來使用函數直接量是一種比較快捷的方法。

通過function語句定義的函數稱為命名式函數、聲明式函數或函數常量,而通過匿名方式定義的函數稱為引用式函數或函數表達式,而把賦值給變量的匿名函數稱為函數對象,把該變量稱為函數引用。