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