Sean's Blog

An image showing avatar

Hi, I'm Sean

這裡記錄我學習網站開發的筆記
歡迎交流 (ゝ∀・)b

LinkedInGitHub

Understand JavaScript #10 函式就是物件 (Functions Are Objects)

本文主要內容為探討「函式物件」的相關知識,包含「一級函式」這個讓 JavaScript 適合撰寫 Functional Programming 的特性,以及函式陳述式、函式表達式、匿名函式等重要觀念。

一級函式 (First-class Functions)

  • 一級函式:可以用別的型別(字串、數值、布林)做到的事情,都可以用函式做到
    • 例如:可以指派一個變數的值為函式;可以將函式作為參數傳入另一個函式;可以用實體語法 (Literal Syntax) 立即創造函式

函式是一個特殊的物件,以下稱為「函式物件」。

與物件相同,函式物件也會被放進記憶體中,而除了物件的特色外,它還有隱藏的特殊屬性 Name (optional) 與 Code(程式屬性),其中的 Code 屬性為 Invocable,即可以透過 () 呼叫,執行設定在這個屬性內的程式碼。

我們平常撰寫函式的程式碼,其實並不是在撰寫函式物件,我們只是在撰寫函式物件中的 Name 與 Code 這兩個「屬性」而已(可以想像函式只是程式碼的容器)。

First Class Functions

我們來做點事情,幫助理解以上的觀念好了。

我們新增一個函式物件,其中 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
First Class Functions Example

其實在函式加上屬性這個動作,在其他程式語言是不可能出現的,但在 JavaScript 中因為函式就是物件,所以可以做到。

函式陳述式 (Function Statement) vs. 函式表達式 (Function Expression)

在 JavaScript 的函式物件中,有函式陳述式與函式表達式。

  • 陳述式 (Statement):會做某件事
  • 表達式 (Expression):是程式碼的單位,會「回傳」或者說是「形成」一個值

表達式 (Expression)

表達式回傳的值不一定要儲存在變數中(記憶體中)。

以下最後兩段程式碼都會形成一個值,一個用變數儲存,另一個沒有。但是這兩行程式碼都是表達式,因為它們都有回傳值。

1var a;
2a = 3; // Return: 3 → 表達式
31 + 2; // Return: 3 → 表達式

還記得運算子就是一個函式吧? 像是等號運算子就是將右邊的值傳給左邊,並將左邊的值設定在記憶體中,然後回傳右邊的值。

陳述式 (Statement)

陳述式的裡面可以再使用表達式,但它本身還是一個陳述式。

像是 if 陳述式就是達到條件的話會做某件事,它是一個陳述式,不會回傳任何值。

而在 if(){...} 的小括號裡的 a === 3 則是表達式,它會回傳 true 或 false,用來判斷是否達成條件。

1// If Statement
2if (a === 3 /* Expression */) {
3  // do something...
4}

匿名函式

  • 匿名函式 (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();

第 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};

在執行階段,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});

如果想要執行傳入的函式,可以改成使用小括號來呼叫函式。

這邊 log() 裡面的 function(){...} 是屬於函式表達式,因為裡面的 function 會形成一個值作為參數,傳給 log 這個 Function 使用。

1function log(a) {
2  a();
3}
4
5// hi
6log(function () {
7  console.log('hi');
8});

像以上這樣把函式傳給另一個函式的做法,就是 Functional Programming 的概念。

回顧

看完這篇文章,我們到底有什麼收穫呢?藉由本文可以理解到…

  • 函式除了有物件的特色外,還有隱藏的特殊屬性 Name 與 Code
  • 函式陳述式與函式表達式
  • 如何建立與使用匿名函式
  • 認識 Functional Programming 的概念

References