Understand JavaScript #10 函式就是物件 (Functions Are Objects)
本文主要內容為探討「函式物件」的相關知識,包含「一級函式」這個讓 JavaScript 適合撰寫 Functional Programming 的特性,以及函式陳述式、函式表達式、匿名函式等重要觀念。
一級函式 (First-class Functions)
- 一級函式:可以用別的型別(字串、數值、布林)做到的事情,都可以用函式做到
- 例如:可以指派一個變數的值為函式;可以將函式作為參數傳入另一個函式;可以用實體語法 (Literal Syntax) 立即創造函式
函式是一個特殊的物件,以下稱為「函式物件」。
與物件相同,函式物件也會被放進記憶體中,而除了物件的特色外,它還有隱藏的特殊屬性 Name (optional) 與 Code(程式屬性),其中的 Code 屬性為 Invocable,即可以透過 ()
呼叫,執行設定在這個屬性內的程式碼。
我們平常撰寫函式的程式碼,其實並不是在撰寫函式物件,我們只是在撰寫函式物件中的 Name 與 Code 這兩個「屬性」而已(可以想像函式只是程式碼的容器)。
我們來做點事情,幫助理解以上的觀念好了。
我們新增一個函式物件,其中 greet
就是函式物件的 Name 屬性,而函式的內容就是 Code 屬性。
如果我們加上 ()
變成 greet()
, 就能呼叫函式,讓函式執行。而因為函式就是物件,所以我們也可以透過點運算子來存取屬性。
1function greet() { 2 // Name: greet 3 console.log('Hi'); // Code 4} 5 6greet.language = 'english'; 7 8console.log(greet); // 這只會得到函式的所有文字 9console.log(greet.language); // english 10
其實在函式加上屬性這個動作,在其他程式語言是不可能出現的,但在 JavaScript 中因為函式就是物件,所以可以做到。
函式陳述式 (Function Statement) vs. 函式表達式 (Function Expression)
在 JavaScript 的函式物件中,有函式陳述式與函式表達式。
- 陳述式 (Statement):會做某件事
- 表達式 (Expression):是程式碼的單位,會「回傳」或者說是「形成」一個值
表達式 (Expression)
表達式回傳的值不一定要儲存在變數中(記憶體中)。
以下最後兩段程式碼都會形成一個值,一個用變數儲存,另一個沒有。但是這兩行程式碼都是表達式,因為它們都有回傳值。
1var a; 2a = 3; // Return: 3 → 表達式 31 + 2; // Return: 3 → 表達式 4
還記得運算子就是一個函式吧? 像是等號運算子就是將右邊的值傳給左邊,並將左邊的值設定在記憶體中,然後回傳右邊的值。
陳述式 (Statement)
陳述式的裡面可以再使用表達式,但它本身還是一個陳述式。
像是 if 陳述式就是達到條件的話會做某件事,它是一個陳述式,不會回傳任何值。
而在 if(){...}
的小括號裡的 a === 3
則是表達式,它會回傳 true 或 false,用來判斷是否達成條件。
1// If Statement 2if (a === 3 /* Expression */) { 3 // do something... 4} 5
匿名函式
- 匿名函式 (Anonymous Function):沒有 Name 屬性的函式
建立一個函式物件,設定它等於一個變數 anonymousGreet
,這個變數在記憶體中有一個指向的位址,這個位址連接著這個函式物件。
接著,等號運算子讓函式物件放進記憶體後,指向 anonymousGreet
變數的記憶體位址。
此時 function
不需要寫 Name 屬性,因為已經有連結函式物件的位址的 anonymousGreet
變數名稱了,它可以作為使用時的參照,所以不必再設定 Name 屬性作為參照,而這就稱為匿名函式。
1greet(); 2 3// Function Statement 4function greet() { 5 console.log('hi'); 6} 7 8// Function Expression 9var anonymousGreet = function () { 10 console.log('anonymous hi'); 11}; 12 13anonymousGreet(); 14
第 3 行的 function greet(){}
是函式陳述式,當程式執行時,它不會做任何事情,JavaScript 就只是把它加到記憶體中,接著繼續往下解析。
第 9 行 anonymousGreet
的等號後面的匿名函式(也就是 function(){}
這個部分)則是函式表達式,因為它會創造一個值(函式物件)給變數。
另外,如果把 anonymousGreet()
的調用移到宣告之前會出現錯誤訊息,而非像第一行一樣提升。
1anonymousGreet(); // Uncaught TypeError: undefined is not a function 2 3var anonymousGreet = function () { 4 console.log('anonymous hi'); 5}; 6
在執行階段,JavaScript 會依序將「函式陳述式」與「變數」放進記憶體,但變數此時是預設值 undefined。
因此第一段 greet()
可以呼叫到完整的函式,然而試著以函式的方式呼叫 anonymousGreet
的時候,就只能找到變數 anonymousGreet
,而且此時的值為 undefined。
函式語言程式設計 (Functional Programming)
我們可以把各型別的值傳入函式作為參數,甚至可以傳一個函式物件。
1function log(a) { 2 console.log(a); 3} 4 5log(3); // 3 6log('Hello'); // Hello 7 8// {greeting: "hi"} 9log({ 10 greeting: 'hi', 11}); 12 13// ƒ () { console.log('hi') } 14log(function () { 15 console.log('hi'); 16}); 17
如果想要執行傳入的函式,可以改成使用小括號來呼叫函式。
這邊
log()
裡面的function(){...}
是屬於函式表達式,因為裡面的function
會形成一個值作為參數,傳給log
這個 Function 使用。
1function log(a) { 2 a(); 3} 4 5// hi 6log(function () { 7 console.log('hi'); 8}); 9
像以上這樣把函式傳給另一個函式的做法,就是 Functional Programming 的概念。
回顧
看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到…
- 函式除了有物件的特色外,還有隱藏的特殊屬性 Name 與 Code
- 函式陳述式與函式表達式
- 如何建立與使用匿名函式
- 認識 Functional Programming 的概念