前言
現在我們知道,在 JavaScript 內函數就是物件,接著要運用這個觀念,來做一些實際運用!但在開始之前,需要先了解函式陳述句 (Function Statements)、什麼是函式表示式 (Function Expressions) 之間的用法差異。
表示式 (Expressions)
表示式為程式碼的單位,會回傳一個值 (A unit of code that results in a value)
白話來說,函式表示式或者任何形式的表示式最終會創造一個值,然而這個值不一定要儲存在某個變數,且這個值可以是任何東西。
舉個例子,我們在 .js 檔內宣告變數 a
並且打開開發者工具,輸入以下:
這是一個簡單的表示式,我們把 3 透過等號運算子賦予給變數 a
,並且執行它,得到回傳的結果。
我們說過,值不一定要儲存在某個變數,因此這樣也是表示式:
這個表示式回傳了 3 ,我們並沒有使用等號運算子將這個值放入變數。
然而,表示式也可以是這個形式
陳述句 (Statements)
當我們提到陳述句,陳述句代表「會做某件事」。
1
2
3
4 var a;
if (a === 3){
}
當 if (a === 3)
就做某件事情。
在 if 陳述句的括號內,必須放進表示式產生一個值,這樣這個陳述句才能運作。
另外陳述句本身不會回傳任何值。
像是我不能這麼做,這是無效的,因為沒有任何值會被回傳給變數 b 。
1 | // 錯誤示範 |
簡單來說,陳述句會做其他事;表示式則回傳值
函式表示式與函式陳述句的差異
接著我們來看看這兩者之間的差異,直接看例子。
函式陳述句
1 | function greet() { |
這樣就是一個簡單的函式陳述句,當創造執行環境時, greet
函式被放進記憶體中,但因為 greet
是函式陳述句,所以不會回傳任何值,直到函式被呼叫執行。
雖然函式陳述句不會回傳任何值,但它會有提升 (hoisting) 現象,所以可以在任何地方取用它,像這樣:
1 | greet(); |
函式表示式
1 | var anonymousGreet = function greet() { |
宣告一個 anonymousGreet
變數並且使用等號運算子,然後在右側使用函式陳述句。
記得我們說的「函式就是物件」,所以可以當作「我們建立了一個物件,並設定它等於這個變數」,也就是這個變數在記憶體中指向的位址。
另外,我們已經有一個已經知道函式物件位址的變數 anonymousGreet
,所以等號右邊的陳述句可以改寫成這樣,稱為匿名函式。
1 | var anonymousGreet = function() { |
如何觸發函式表示式呢
我們需要指向該物件,並且告訴它執行程式,像是這樣
1 | anonymousGreet(); |
因為變數已經知道了函式物件的記憶體位址,只需要加上()來呼叫函式就可以執行了。
另外還有一個值得注意的問題,函式表示式的提升 (hoisting) 現象,如果我們將程式改成這個樣子:
1 | anonymousGreets(); |
結果會變成這樣,為什麼呢?
還記得當執行環境被創造,創造執行階段會把函式陳述句以及變數都放入記憶體,變數被賦予初始值 undefined
,然後逐行執行程式碼。
於是程式的第一行是「anonymousGreets();
」,但此時仍未賦予變數值,變數的值仍然是 undefined
。自然的,錯誤便會告訴我們 undefined
不是函式,它沒辦法被使用 () 呼叫執行。
1 | var anonymousGreets = function() { |
直到上述這行程式碼,anonymousGreets
變數的值才被賦予函式物件。
代表函式表示式不受到提升 (hoisting) 影響。
傳入函式表示式做為參數
記得我們說的函式是物件,函式表示式可以馬上創造函式物件,因此我們可延伸出以下寫法:
1 | function log(a){ |
我們立即創造了一個函式物件,在裡面寫了一些程式碼。然後把這個函式物件當成參數傳入 log
函式內。
不過這樣只是印出函式物件的內容而已,但透過這樣的觀察得知「一級函式可以很快地被創造、使用,且變數也可以設值成為一級函式」
我們結合上述這些並做些修改:
1 | function log(a){ |
因為我們傳入 log
函式的參數為函式物件,所以變數 a
參照到了這個函式物件。同樣地,要呼叫執行函式僅需要加上 () 即可。
本例來看,我使用函式表示式,接著把這個函式傳入當作另一個函式的參數,這樣另一個函式就可以使用這個函式表示式,這就是我們提到的一級函式的觀念「可以將函式傳入別處」。
可以把函式給另一個函式,就像使用變數一樣,這樣的做法也稱為函式程式語言 ( functional programming )。