1. 程式人生 > >JavaScript之函式型別

JavaScript之函式型別

讓我們直接進入函式宣告和函式表示式,看看如何在JavaScript中使用這兩種型別的函式。如果不仔細看,兩者可能非常相似:

//函式宣告function isLie(cake){ return cake === true;}//函式表示式var isLie = function(){ return cake === true;}

二者之間唯一的區別發生在賦值期間。直譯器可以在語法解析期間訪問到函式宣告。而函式表示式是賦值表示式的一部分,在整個程式賦值完成前都不能執行。這點區別看起來很小,但是影響很大。考慮一下這種情況:

declaration(); //我是一個函式宣告function declaration
(){ console.log('我是一個函式宣告');}expression(); //Uncaught TypeError: undefined is not a functionvar expression = function(){ console.log('我是一個函式表示式');}

就像前面的例子裡所看到的,函式表示式在被呼叫時丟擲了異常,但函式宣告執行正常。這個異常揭示了函式宣告和函式表示式的關鍵不同。JavaScript可以理解宣告的函式,並能在程式執行前將函式解析。因此,函式的呼叫在宣告前後都沒關係,因為JavaScript在幕後已經將函式提升到當前作用域的頂端。而函式表示式直到變數被賦值時才進行計算,因此當它被呼叫時仍是undefined。所以優秀的程式碼風格應該在當前作用域的頂端定義所有變數。這樣做的話,指令碼的順序就跟JavaScript語法解析時的順序就一樣了。

前面省略了一個概念,當語法解析時,JavaScript會把所有函式宣告移到當前作用域的頂端。所以宣告式函式可以放在指令碼的任意位置。為了更進一步探索宣告和表示式之間的區別,看看這個例子:

function sayHi(){ console.log('hi');}var hi = function sayHi(){ console.log('hello');}hi(); //hellosayHi(); //hi

你在看這段程式碼的時候可能會以為函式宣告會崩潰,因為它跟函式表示式重名了。然而因為第二個函式是賦值變數的一部分,擁有自己的作用域,所以JavaScript把它們當成不同的實體。

下面的例子可能更讓人困惑:

var sayHo;console.log(typeof(sayHey)); //functionconsole.log(typeof(sayHo)); //undefinedif(true){ function sayHey(){ console.log('hey'); } sayHo = function sayHo(){ console.log('ho'); }}else{ function sayHey(){ console.log('no'); } sayHo = function sayHo(){ console.log('ho'); }}sayHey(); //nosayHo(); //no

在前面的例子中,你已經看到了擁有相同名字的函式宣告和函式表示式會被區別對待。在這個例子中,我會嘗試基於程式執行的方式來按條件定義函式。看到這段指令碼的控制流時,你可能會期待sayHey返回‘hey’,因為條件宣告的執行結果為真。試試恰恰相反,返回的結果是‘no’,也就是說第二個版本的sayHey函式取代了第一個。更讓人困惑的是sayHo函式的行為完全相反,這可以再次歸結為編譯時和執行時之間的差異。

你已經知道JavaScript會在解析指令碼時把所有的函式宣告放到當前作用域的頂端。在這個過程中,處在同一個作用域的第二個函式取代了第一個函式。這就是為什麼會返回‘no’。你也知道函式表示式在賦值過程完成前都會被解析器忽略。賦值跟條件宣告的計算一樣在執行時發生。這就是為什麼sayHo函式可以按條件定義。這裡的關鍵是函式宣告不能被條件式的定義。如果你需要條件定義,那就應該用函式表示式。此外,函式宣告永遠不應該放到控制流語句裡,因為不同直譯器的處理方式會不一樣。