---
title: JavaScript核心篇 - 函式
tags:
description:
---
JavaScript核心篇 - 函式
===
### 函式本身 function
- 可以執行片段程式碼。
- 可以重複呼叫。
- 可以傳入不同的參數,改變執行的結果。
```javaScript=
function callSomeone(name) {
console.log(`嘿,這是${name}`);
};
callSomeone('小明'); // 嘿,這是小明
```
- 函式本身也是一個物件。
```javaScript=
callSomeone.myName = '我是Ron';
console.dir(callSomeone);
```

- 限制作用域。
- 函式可以回傳值(**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行回傳的結果只有函式。

再把**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`。這個物件裡面**又包含了多個函式**(方法)。

---
### 使用`立即函式`創造獨立的環境(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);
};
```