---
# System prepended metadata

title: JavaScript核心篇 - 函式

---

---
title: JavaScript核心篇 - 函式
tags: 
description:
---
JavaScript核心篇 - 函式
===

### 函式本身 function
 - 可以執行片段程式碼。
 - 可以重複呼叫。
 - 可以傳入不同的參數，改變執行的結果。
 ```javaScript=
 function callSomeone(name) {
  console.log(`嘿，這是${name}`);
};
callSomeone('小明');     // 嘿，這是小明
 ```
 - 函式本身也是一個物件。
 ```javaScript=
callSomeone.myName = '我是Ron';
console.dir(callSomeone);
 ```
 ![](https://i.imgur.com/8GQK4P8.png =50%x)
 
 - 限制作用域。
 - 函式可以回傳值（**return**）。

---

### 立即函式
- 限制作用域。
- 立即執行。
- 可以回傳一個結果。



:::success
**函式陳述式**一定要有函式名稱（具名函式）。

立即函式內沒有函式名稱（匿名函式），所以是**函式表達式**。
:::

---

### 一級函式
:::success
**函式**在該語言可**被視為與其他變數一樣時**，就可以稱該語言具有**一級函式**。
:::

- 函式可作為**變數**（函式表達式）。

  ```javascript=
  const fn = function (name){
    return `hello ${name}`;
  };
  console.log(fn('joe'));    // hello joe

  // 賦予給其他變數
  let str = fn('joe'); 
  console.log(str);    // hello joe
  ```
  
- 函式可做為**參數**使用
  
  - 函式當參數傳遞就是**callback Function**
    - 第2行`callbackFunction()`，就是第5行`fn()`代入的匿名函式。
    
  ```javascript=
   const fn = function (callbackFunction){
     callbackFunction();
   };
    // 把匿名函式代到fn裡面去執行
   fn(function() {
     console.log(`這是callback`);
   }); 
  ```
  - 進階的**callback Function**
  
    - 匿名函式代入fn()。
    - `callbackFunction(a, b)`取得`const a`、`const b`組成字串。
    
  ```javascript=
    function fn(callbackFunction) {
      const a = 'joe';
      const b = 'jojo';
      callbackFunction(a, b);
    };

    fn(function (x, y) {
      console.log(`${x},${y}是好朋友`);
    });
  ```
  
- 函式可以作為回傳值（函式裡還能回傳函式）
    ```javascript=
    function fn() {
      return function () {
        console.log(`這是回傳的函式`);
      }
    };

    console.log(fn());
    ```
    第7行回傳的結果只有函式。
    
    ![](https://i.imgur.com/HNqqj01.png =50%x)
    
    再把**return**的函式執行
    ```javascript=7
    fn()();    // 這是回傳的函式
    ```
    
---

### 高階函式（技法）

:::success
特性跟一級函式相同，高階函式是指用的寫法、技法（概念）。
:::


- 商品與商品折扣寫法
  - 第8行，把`itemPrice()`帶入價格100，賦予給變數
  `brandPrice`。
  
  - 第10行，`brandPrice(0.5)`會呼叫`return`的函式，並且把折扣代入。
  
  - `itemPrice（）`內的匿名函式，會從外層取得`originPrice`，跟value計算取得折扣後價格，並回傳。
    ```javascript=
    function itemPrice(price) {
      const originPrice = price;
      return function(value) {
        return originPrice * value;
      };
    };

    const brandPrice = itemPrice(100)

    console.log(brandPrice(0.5));    // 50
    console.log(brandPrice(0.8));    // 80
    ```

---

### 閉包（closure）

:::success
1. 閉包就是**內層函式**可以**取得外層函式作用域內**的變數。反過來說，外層函式**無法存取內層函式**作用域內的變數。

2. 閉包**作用域內**的變數，若有被**內層匿名函式調用**。則閉包作用域內變數的記憶體**不會被釋放掉**。

3. 閉包作用域內**反覆調用**的變數，也稱為**私有**（private ）變數。
:::

- 內層函式調用外層的變數`str`
    ```javascript=
    function fn() {
      let str = `內層函式訪問外層作用域，稱為閉包。`;
      return function() {
        debugger;
        console.log(str);
      };
    };

    const x = fn();

    x();    // 內層函式訪問外層作用域，稱為閉包。
    ```

- 透過高階函式特性（回傳函式），將作用域內的變數**不被釋放**。
    ```javascript=
    function sellEgg(price) {
      const eggPrice = price;
      let count = 0;
      return function (value) {
        // 會調用外層變數count
        count += value;
        console.log(`一共賣出${count}個蛋，賺了${eggPrice * count}元`);
      };
    };

    // 把函式sellEgg()代入雞蛋價格15元，並賦予給變數todaySell
    const todaySell = sellEgg(15);
    // 再把雞蛋數量10，賦予到sellEgg()內層函式
    todaySell(10);

    // 假設下週雞蛋變便宜，代入雞蛋價格13元，並賦予給變數nextWeekSell
    const nextWeekSell = sellEgg(13);
    nextWeekSell(10);
    ```
    從上面範例可以知道**閉包的特性**:point_down: 
    - **可重複調用**
      - 能夠賦予給不同變數使用。
    - **獨立變數**
      - 在第6、17行，閉包賦予給不同變數之後，閉包與閉包之間，作用域內的資料都**完全獨立**。
    - **隱藏變數**
      - 上面範例第2行，eggPrice是**被封裝**在`sellEgg()`作用域內，**全域**或**其他函式**都無法調用。

---

### 閉包私有方法

在下面範例中
- 創造一個wallet的函式（物件），並用**3種方法**存取內層變數`money`。
- 這裡的變數`money`就相當於物件導向中的**私有成員變數** (private member)。

```javascript=
function wallet(initValue) {
    //  wallet()不帶參數的話，預設值就是0
  let money = initValue || 0;
  return{
    increase: (value) => {
      money += value;
      console.log(`錢包增加了${value}元，現在剩${money}元`);
    },
    decrease: (value) => {
      money -= value;
      console.log(`錢包減少了${value}元，現在剩${money}元`);
    },
    watch: () => {
      console.log(`現在錢包一共有${money}元`);
    },
  };
};

// wallet(1000)初始給錢包1000元，賦予給變數fatherWallet
const fatherWallet = wallet(1000);

// 實際是去操作fatherWallet裡return的物件
fatherWallet.increase(100);
fatherWallet.decrease(500);
fatherWallet.watch();

```
- const `fatherWallet`實際上是==物件==，因為`wallet(1000)`等同定義一個作用域裡面的物件**return**給`fatherWallet`。這個物件裡面**又包含了多個函式**(方法)。

  ![](https://i.imgur.com/dT4AYh4.png)

---

### 使用`立即函式`創造獨立的環境(Immediately Invoked Function Expression, IIFE)

這段程式碼運行時，會印出什麼？ 

```javascript=
for (var i = 0; i <= 5; ++i) {
  setTimeout(function() {
    console.log(i)
  }, 1000 * i)
}
```

直覺想到會印出1、2、3、4、5。  但實際上會每一秒印出連續五個`5`，


**為什麼？**

1. 在`for`的**作用域內**的`setTimeout()`還未執行時，`for`迴圈已經跑完5次，會產生5個`setTimeout()`。

2. `var i` 是全域變數，`for`迴圈跑完五次時，`i = 5`，

3. 每隔**1秒**`setTimeout()`內的**callback function**調用`i`時，取到的值是`5`。

<br>

**如果要每隔一秒印出1、2、3、4、5**該怎麼做？

重點是要讓每個**callback function**有自己的變數`i`

:::success
- 利用閉包的特性，利用**立即函式**將`setTimeout()`變成獨立的環境。

    ```javascript=
    ((x) => {
       setTimeout(() => {
          console.log(x);
       }, 1000 * x);
    })(i)
    ```

- 執行立即函式參數`i`傳入後，複製到箭頭函式參數`x`，讓`setTimeout()`內的**callback function**調用參數`x`，因此參數`x`在**IIFE作用域**不會被釋放掉。

- 因而`setTimeout()`的**callback function**可以取得各自的變數`x`。

<br>

完整程式碼
```javascript=
for (var i = 1; i <= 5; i++) {
  ((x) => {
    setTimeout(() => {
      console.log(x);
    }, 1000 * x);
  })(i)
};
```
:::

另一種解決方式就是把`var i`改成`let i`

改成`let i`之後`i`的作用域就被限制在`for`迴圈的**作用域內**

每次執行`setTimeout()`的**callback function**便能在**外層**取得各自的變數`i`。


```javascript=
for (let i = 1; i <= 5; i++) {
  setTimeout(() => {
    console.log(i);
  }, 1000 * i);
};
```
