JavaScript 函數表達式
JavaScript中創建函數主要有兩種方法:函數聲明和函數表達式。這兩種方式都有不同的適用場景。這篇筆記主要關注的是函數表達式的幾大特點以及它的使用場景,下面一一描述。
主要特點
- 可選的函數名稱
函數名稱是函數聲明的必需組成部分,這個函數名稱相當於一個變量,新定義的函數會複製給這個變量,以後函數的調用都需要通過這個變量進行。而對於函數表達式來說,函數的名稱是可選的,例如下面的例子:
1 2 3 |
|
這個例子中函數表達式沒有名稱,屬於匿名函數表達式。再看下面的例子:
1 2 3 4 5 |
|
在這個例子中,函數表達式的名稱爲f,這個名稱f實際上變成了函數內部的一個局部變量,並且指代函數對象本身,在函數遞歸的時候有很大用處,後面會詳細講到。
- 在執行階段創建(區別於函數聲明)
這個特點是函數表達式明顯區別於函數聲明的地方。
解釋器在解析JavaScript代碼時對於這兩種方式並不是一視同仁的。解釋器會首先讀取函數聲明,並使其在執行任何代碼之前可用;而對於函數表達式,則必須等到解釋器執行到它所在的代碼行,纔會被真正解析執行。例如:
1 2 3 4 5 6 7 8 |
|
第一條語句完全可以正常執行。對代碼求值時,JavaScript引擎在第一遍就會聲明函數並通過一個名爲函數聲明提升的過程將它們放到源代碼樹的頂部。也就是說在執行環境的創建階段(函數被調用但還沒有開始執行)就會對函數聲明進行"hosting"操作。所以,即使聲明函數的代碼在調用它的代碼後面,JavaScript引擎也會把函數聲明提升到頂部。但是如果把函數聲明更改爲函數表達式,就會在執行期間報錯。原因在於在執行到函數所在的語句之前,變量sub中並不會包含對函數的引用。也就是說在代碼執行階段,變量sub纔會被賦值。除了以上不同,在其它方面函數聲明和函數表達式的語法是等價的。
- 不會影響變量對象
1 2 3 4 5 |
|
通過上面的例子可以看到,函數名稱f只能在函數對象內部使用,函數表達式的函數名稱並不存在於變量對象中。
使用場景
函數表達式的使用場景很多。下面主要描述的是函數遞歸以及代碼模塊化方面的應用。
- 函數遞歸
看下面的例子:
1 2 3 4 5 6 7 |
|
這是一個經典的階乘函數,但是這個例子存在的一個問題是函數名稱factorial與函數體緊密耦合在一起,執行下面的語句就會報錯:
1 2 3 |
|
報錯的原因在於在函數體內部會調用factorial函數,而變量factorial對函數的引用已經被解除所以報錯。這種情況的解決方法一般可以使用arguments.callee來解決,arguments.callee始終指向當前的函數,例如:
1 2 3 4 5 6 7 |
|
這樣在此執行anotherFactorial函數就可以得到正確結果了。但是在嚴格模式"strict"下,arguments.callee是不能通過腳本訪問的,這是就可以使用函數表達式來解決這個問題了,例如:
1 2 3 4 5 6 7 8 |
|
-
代碼模塊化
JavaScript中沒有塊級作用域,但我們可以使用函數表達式模塊化JavaScript代碼。模塊化代碼中可以封裝不必讓使用者知道的細節,只暴露給使用者相關接口,同時可以避免對全局環境的污染,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
這個例子中創建了一個匿名函數表達式,這個函數表達式中包含了模塊自身的私有變量和函數;這個函數表達式的執行結果返回一個對象,對象中包含了模塊暴露給使用者的公共接口。代碼模塊化的具體形式還有很多,例如在一些常用的JavaScript庫中通常都會使用類似下面例子的立即執行函數:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
這種方式直接將包含模塊公共接口的對象作爲全局對象的一個屬性,這樣在其它地方直接可以使用全局對象的這個屬性來使用這個模塊了。
這篇筆記只包含了JavaScript中函數表達式的一部分內容,可能存在不準確或錯誤的地方,希望能夠指出!