---
title: 'JS 核心 15 - 函式、立即函式'
tags: JS 核心 ,JS , JavaScript, this
description: 2021/02/11
---
JS 核心 -- 函式、立即函式
===
## 什麼是函式
透過 function 宣告一個函式,此函式所宣告出來的物件跟一般的物件有些不同。
1. 被呼叫的能力 : 在函式名稱後方加上括號(用來傳參數使用),此時多了被呼叫的能力。在呼叫函式的同時,也可以把參數傳進來。
2. 包含程式碼的片段 : 函式的 { } 裡的內容是程式碼的片段,也是函式所擁有,一般物件並沒有包含這段。
3. 有回傳的功能 : 函式可以 return 回傳一段值 (可以是純值或物件)
:pencil2: **使用 function 關鍵字宣告字詞時,此字詞就會變成一段函式,此函式就包含以下能力。**

```
function afunction(parameter) {
var localVariable = '區域變數';
console.log(this, localVariable); // this (如下圖)、區域變數
return '附加一段' + parameter; // 回傳參數內容
}
var data = afunction('參數'); // 這段是表達式,會回傳一個值,此值再賦予到變數 data 上
console.log(data); // 附加一段參數 (data 的值來自於函式的回傳)
```
window 是此 function 的 this 所呈現的值

## 函式陳述式 (又稱具名函式)
1. 透過「函式陳述式」的方式來建立一段函式
2. 直接透過 function 來宣告一個函式
3. <span class="red">**要求一定要補上名稱,否則無法呼叫。此具有名稱的函式又稱為「具名函式」**</span>
```
function functionA() { // 使用 function 關鍵字來定義一段函式
console.log('函式陳述式', '具名函式');
console.log(functionA);
}
functionA(); // 呼叫此段函式
```
## 函式表達式 (又稱匿名函式)
1. 先宣告一個變數
2. 使用 function 關鍵字來定義一段函式的物件 (此函式沒有名稱)
3. 這段函式物件透過「等號運算子」賦予到 functionB 的變數上
4. 變數 functionB 接收的是此段函式的參考路徑,這參考路徑下的函式是沒有名稱的 (又稱匿名函式)
5. 此段函式物件可以被呼叫
6. 不是所有的「函式表達式」都是「匿名函式」
```
var functionB = function() { // 使用 function 關鍵字來定義一段函式
console.log('函式表達式', '匿名函式');
console.log(functionB);
}
functionB(); // 執行此段函式
```
## 「具名函式」和「匿名函式」的名稱在哪裡呢 ?

### 「具名函式」:
* functionA 來自函式的片段
* 在 " f " 後面可以看到 functionA 這個名稱,裡面也包含程式碼的片段
### 「匿名函式」:
* 在 " f " 後面沒有名稱,裡面直接帶入程式碼的片段
## 函式表達式中的具名函式
### :pencil2:「具名函式」可以在函式內被調用
```typescript=
var functionC = function functionD() {
console.log(functionC, functionD); // 具名函式 functionD 能夠在函式內被調用
}
functionC(); // 執行這段函式
```
1. 先宣告 functionC 的變數,後面帶入另一段函式的表達式,這段函式宣告有給一個名稱 functionD
2. functionD 是函式的名稱,functionD 此「具名函式」可以在函式內被調用
3. (如下圖) " f " 後面出現 functionD 的名稱。雖然宣告的變數是 functionC ,但實際上函式的名稱是 functionD,所以 console.log(functionC, functionD); 印出的結果都是顯示 functionD 這個名稱。因為 functionD 是函式的名稱,並非指變數。

若把 console.log(functionC, functionD); 貼到外層就有不一樣的結果。
* **functionC 在外層可以執行,變數 functionC 是接收函式 functionD 的內容 (接收 functionD 的參考位置)。**
* <span class="red">**functionD 只能在函式內被調用,在外層是取不到 functionD 這個名稱的。**</span>
```typescript=5
console.log(functionC);
// 可以執行,變數 functionC 是接收函式 functionD 的內容 (接收 functionD 的參考)
console.log(functionD); // functionD is not defined (在外層取不到 functionD)
```
### :pencil2: 範例
1. 先宣告 giveMeMoney 的變數,後面帶入一段 giveMoreMoney 函式
2. giveMoreMoney 是具名函式的名稱,並非為 giveMeMoney 的函式 (具名函式可在函式內被調用)
```
var num = 1;
var giveMeMoney = function giveMoreMoney(coin) {
num += 1;
console.log('執行 giveMeMoney', num, coin);
return coin > 100 ? coin : giveMoreMoney(num * coin);
// 判斷式,當傳入金額不夠時,就試著增加金額 (觸發 giveMoreMoney 函式),直至超過 100 元才會停止
};
console.log(giveMeMoney(30)); // (結果如下左圖)
```
> <span class="red">函式到底需不需要名稱在於是否可被調用
> 如果可以被調用,那名稱可以被省略</span>
<span class="green">以上例來說直接執行 giveMeMoney 是沒有問題的,那 giveMoreMoney 名稱就可以被省略。</span>
(寫法如下)
```
var num = 1;
var giveMeMoney = function(coin) {
num += 1;
console.log('執行 giveMeMoney', num, coin);
return coin > 100 ? coin : giveMeMoney(num * coin);
};
console.log(giveMeMoney(30)); // (結果如下左圖)
```

### :pencil2: 函式陳述式 (具名函式) 若沒有名稱,則無法執行
```
function callSomeone(fn) {
fn(); // 執行函式 (一定要傳入一個函式,才能執行)
console.log(fn); // ƒ () { console.log('執行函式') }
}
callSomeone(function() { console.log('執行函式') }); // (結果如上右圖)
// 函式陳述式 (具名函式) 一定要有名稱才能被呼叫執行
// 傳入 callSomeone 的參數為一段匿名函式(不需要名稱),因為他傳入變成參數時就如同函式表達式。
// 定義一段函式並賦予到參數 fn 上,參數 fn 接受了此匿名函式。
```
---
## 立即函式 IIFE
1. 不需要呼叫此函式,也能執行
* 立刻執行
2. IIFE 最主要目的是避免污染到全域執行環境並照成污染與衝突
* 限制作用域的用途 (連同函式宣告都能限制作用域)
* 變數的作用域只在函式內,在外層無法取得變數
* 「立即函式」無法在外層呼叫
3. 「立即函式」本身是表達式,所以也能回傳一個值
* 立即可以被呼叫的「函式表達式」,定義完即可執行
### :pencil2: 範例 : 透過「具名函式」的方式來執行的 IIFE,且 IIFE 無法在外層被呼叫
```
(function IIFE() {
console.log('立即函式', IIFE);
}());
console.log(IIFE); // IIFE is not defined (無法在函式外被再次執行)
IIFE; // IIFE is not defined (「立即函式」無法在外層被呼叫)
```

### :pencil2: 範例 : IIFE 也是可自我執行的「匿名函式」
```
(function () {
console.log('立即函式'); // 立即函式
}());
```
小括號的位置也可移至外層
```
(function () {
console.log('立即函式'); // 立即函式
})();
```
### :pencil2: 範例 : 變數只活在 IIFE 內
> 「立即函式」可以避免裡面的變數污染到 global scope。
變數 Ming 宣告在「立即函式」的內層,只能在「立即函式」內被取得,在外層無法取得變數
```
(function() {
var Ming = '小明'; // 透過「立即函式」限制變數的作用域
console.log(Ming); // 小明
})();
console.log(Ming); // Ming is not defined (在外層無法取得變數)
```
### :pencil2: 範例 : 限制作用域的用途
> 就是為了避免命名衝突,而範例中 getName 是屬於全域的函式。因此在開發中,getName 還是可能與其它變數名稱衝突,如果透過立即函式,就連同函式宣告都能限制作用域。
```
function getName() {
var Ming = ‘小明’;
console.log(Ming); // (限制變數的作用域)
};
getName(); // 小明
console.log(Ming); // Ming is not defined
```
### :pencil2: 範例 : 「立即函式」也能傳遞參數
「立即函式」本身是表達式,所以也能回傳一個值
> 可以利用 return 並搭配一個變數來接收 IIFE
```
var whereMing = (function (where) {
console.log(where);
return where; // return 會把值傳出來外層,外層變數就可接收此函式
})('小明在這'); // 透過 "小括號" 把參數往前傳
console.log(whereMing); // 小明在這 (變數 whereMing 接收此函式的 return 結果)
```
### <span class="green">return用法 : </span>
> * 只要你需要將結果回傳時,就會使用到該方式。
> * 這邊主要觀念與執行堆疊的記憶體釋放有關係,當函式執行完畢後就會釋放記憶體,所以若要保留結果,那麼就必須 return。
### :pencil2: 範例 : 「立即函式」不符合 ASI 規則,無法自動插入分號
以下兩個「立即函式」因沒有使用分號隔開被視為同一行
「立即函式」不符合 ASI 規則,無法自動插入分號
```
(function(){
})() // 跳錯,(intermediate value)(...) is not a function
(function(){
})() // 這個括號的內容不是 function
```
解決方法 : 在使用任何「立即函式」的**前方或後方**要補上分號,才能正確執行
```
(function(){
})();
(function(){
})()
```
### :pencil2: 範例 : 把前一個「立即函式」的內容傳至下一個「立即函式」內
「立即函式」傳遞變數的手法
* **使用物件 "傳參考" 的特性傳遞**
```
var a = {}; // 先宣告一個物件,物件有傳參考的特性
(function(b){
b.person = '小明';
console.log(b === a); // true
console.log(b.person === a.person); // true
})(a);
(function(c){
console.log(c.person); // 小明
console.log(c === a); // true
console.log(c.person === a.person); // true
})(a)
```
1. function(b) 和 function(C) 裡面的 b 和 c 參數,都是傳進來 a 的值。
如果將 a 與 b 和 c 比較會發現都為 true。 a 與參數 b、c 的值是一樣的。
2. 有一個 a 的物件帶到b,又因為物件是傳參考,所以 a 和 b 的參考路徑都是相同,所以
b.person=小明; 實際上因參考路徑相同所以 a、b 底下都有一個屬性 person 跟值小明,第二個
立即函式也接收了 a 的參考路徑故 console.log(c.person); //小明 。 a、b、c的參考路徑都是相
同。
* **將參數掛載到 window 全域物件的方式傳遞**
> 將 window 傳入,可以確保框架正確的掛載到全域變數 window 上
>
```
(function(global) {
global.person = '小明';
})(window) // window 是全域物件,把 window 全域物件傳到前面這個「立即函式」
;(function(c) {
console.log(person); // 小明
})()
```
1. 將傳進第一個立即函式的參數改為全域物件 window。第二個立即函式印出全域物件的屬性 person。
2. window 是一個物件,而透過 global 傳入後也是一個物件傳參考特性,因此 global.person 其實就是 window.person。
3. window 是全域物件,把 window 全域物件傳到前面的立即函式,在全域物件掛上屬性 person,並把值傳到另一個立即函式。
```
var greeting = 'Hola';
(function(global, name){
var greeting = 'Hello';
global.greeting = 'Hello' // 讓 Hola 變成 Hello
console.log(greeting+' '+name) // Hello Iris
})(window, 'Iris') // 取用全域中的變數,並代入 IIFE 中來更改全域變數
console.log(greeting) // Hello
```
### :pencil2: 練習 : 執行環境、物件參考觀念
```
var vm = window;
var food = '水牛城雞翅';
window.food = '水牛城雞翅123';
console.log(food); // food = 水牛城雞翅123
console.log(window.food); // food = 水牛城雞翅123
(function(global){
var food = '雞塊'; // 此變數 food 僅存活於立即函式內
global.food = '麥脆雞';
console.log(global === vm); // true
console.log(global.food === food); // false
console.log(food); // food=雞塊
console.log(global.food); // food=麥脆雞
})(window);
console.log(food); // food=麥脆雞
```
1. var 在立即函式內宣告 (以函式當作作用域)
2. 首先 window 本身是一個物件,所以你透過函式參數傳入後其實是屬於物件參考的傳入,也因此才會導致 global.food = '麥脆雞'; 影響到外層的 window。
## :memo: 學習回顧
:::info
* 函式陳述式 (又稱具名函式)
* 這種函式不會回傳任何值,只是執行了一些行為,而且會被提升
* <span class="red">**要求一定要補上名稱,否則無法呼叫。**</span>
* <span class="red">**「具名函式」可以在函式內被調用,在外層是取不到此函式 → 如果可以被調用,那名稱可以省略。**</span>
* 函式表達式 (又稱匿名函式)
* 這邊宣告的變數會被提升 (hoisting),函式不會
* 此段函式物件透過「等號運算子」賦予到 functionB 的變數上
* 此段函式物件可以被呼叫
* <span class="red">**不是所有的「函式表達式」都是「匿名函式」**</span>
* 「立即函式」本身是表達式,所以能回傳一個值
* IIFE 最主要目的是避免污染到全域執行環境並照成污染與衝突
* 限制作用域的用途 (連同函式宣告都能限制作用域)
* 變數的作用域只在函式內,在外層無法取得變數
* 「立即函式」無法在外層呼叫
* 立即可以被呼叫的「函式表達式」,定義完即可執行
* 「立即函式」不符合 ASI 規則,無法自動插入分號
:::
## :+1: 相關參考文件
:::info
[Ray - 函式以及 This 的運作-立即函式](https://hsiangfeng.github.io/javascript/20201118/707576253/)
[Ray - 函式](https://w3c.hexschool.com/blog/cb6e361)
[Kuro - 函式 Functions 的基本概念](https://ithelp.ithome.com.tw/articles/10191549)
:::
<style>
.red {
color: red;
}
.green {
color: green;
}
</style>