---
# System prepended metadata

title: JavaScript - 函式中的參數(parameters)與引數(arguments)
tags: [rest, parameters, arguments, Javascript]

---

# JavaScript - 函式中的參數(parameters)與引數(arguments)

## 函式參數 (parameters) vs. 函式引數 (arguments)

先上 MDN 的說明：
> [參數 parameter](https://developer.mozilla.org/en-US/docs/Glossary/Parameter)：A parameter is a named variable passed into a function. Parameter variables are used to import arguments into functions.

>[引數 argument](https://developer.mozilla.org/en-US/docs/Glossary/Argument)：An argument is a value (primitive or object) passed as input to a function.


「函式參數(parameters)」是定義函式時所列出的變數，用來將引數導入至函式中；「函式引數(arguments)」則是實際上輸入至函式或是函式收到的值。

```javascript
// 定義函式，設定「參數」
function greet(name, age){
    console.log(`${name} is ${age} years old.`)
}

// 呼叫函式，傳入「引數」
greet('Alice', 28) // Alice is 28 years old.
```

上述的例子中 `name` 和 `age` 是參數；`'Alice'` 和 `28` 則是引數。

### 未傳入引數的參數值為 `undefined`
如果定義函式時設定了參數，卻在呼叫函式時沒有傳入引數，函式仍然可以正常運作，不過引數的值會是 `undefined`。
```javascript
// 沒有傳入引數
var print = function (x,y){
    console.log(x,y)
}
print() // undefined, undefined
```
只有傳入一個引數也不會報錯，並且可由此得知 JavaScript 是由左至右讀取參數：
```javascript
print(3)// 3, undefined
```
為什麼沒有傳入引數的時候，參數的值會是 `undefined` 呢？這是由於 Javascript [提升(hoisting)]([hoisting](https://hackmd.io/miMzEpGjQh-ck8t0j_4bHw))的特性。在[函式的提升](https://hackmd.io/miMzEpGjQh-ck8t0j_4bHw?view#%E5%87%BD%E5%BC%8F%E7%9A%84%E6%8F%90%E5%8D%87)中，Javascript 在編譯時會先宣告函式的參數 `name` 和 `age` ，若參數沒有值則會賦予 `undefined` 的值。

## 預設參數 Default Parameters
如果想要避免在呼叫函式時沒傳參數（或是傳入 `undefined`）導致出現 `undefined` 的狀況，ES6 允許我們在定義函式時為參數設定指定的預設值。

透過 `=` 為參數賦予預設值：
```javascript
function greet(name = 'Alice', age = '28') {
    console.log(`${name} is ${age} years old.`)
}

greet() // Alice is 28 years old.
greet('Bob', 18) // Bob is 18 years old.
```
上面的例子中，`greet()` 沒有傳入任何引數，因此函式的參數值為預設值。

不過這樣的寫法不是所有瀏覽器都有支援（例如：IE），保險起見也可以使用另一種寫法：
```javascript
function greet(name, age) {
    name = name || 'Alice' // name 的預設值
    age = age || 28 // age 的預設值
    console.log(`${name} is ${age} years old.`)
}
```
由於在沒有帶入預設值的情況下，參數值為 `undefined`。這時藉由 [`||` 運算子](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Logical_OR)來為參數賦值：當 `||` 左側 `undefined`(falsy value) 被強制轉型成 `false`時，會回傳 `||` 右側的值，也就是我們希望的預設值（`number = Alice`、`age = 28`）。

## 其餘參數 rest parameters
[其餘參數(rest parameters)](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters)可以讓我們用來**表示不確定數量的參數**，並將其**視為一個陣列**。

```javascript
function(a,b,...args){
    // ...
}
```

每當我們使用 `...args` 作為最後一個傳遞給函式的參數時，表示剩餘的引數物件 (arguments object) 會被轉為陣列。`...`為「其餘運算子(rest operator）」，`args` 可以是任何名稱。
```javascript
function fn(a,b, ...restArgs) {
    console.log('a', a) // a 1
    console.log('b', b) // b 2
    console.log('more args:', restArgs) // more args: [3,4,5]
}

fn(1,2,3,4,5)
```

### 使用其餘參數將 arguments 轉為陣列
```javascript
function fn(...args) {
    console.log(args) // [1,2,3]
    console.log(args[0]) // 1
    console.log(args.reverse()) // [3,2,1] 
}

fn(1,2,3)
```
上面的例子中，我們在定義函式 `fn` 時使用了其餘參數 `...args`。

當我們將 1,2,3 作為引數傳遞給函式 `fn()` 時， `args` 會將傳入的引數蒐集在一個陣列中，讓我們能夠以陣列的形式獲得 arguments 物件。

如此一來，我們就可以在 `fn()` 函式內部取用 `args` 陣列，並對其使用陣列的各種操作方法，像是[`reverse()`](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/reverse) 將陣列原地反轉，或是排序 `sort()`、過濾 `filter()` ⋯等。

## 引數物件 arguments

> `arguments` is an array-like object accessible inside functions that contains the values of the arguments passed to that function.

`arguments` 引數物件是傳入函式的一種**類陣列物件(Array-like object)**，包含著「所有」傳入函式的值。

有時候在定義函式時我們並不確定需要設定幾個參數， `arguments` 讓我們在定義函式時就算未設定參數，仍然可以透過 `arguments` 物件取得呼叫函式時所傳入的引數。

```javascript
function fn(a,b,c) {
    console.log(arguments)
    console.log(arguments[0])
}
fn(1,2,3)
```
![](https://hackmd.io/_uploads/H1gCCGiT3.png)

這邊提到「`arguments` 會傳入『所有』傳入函式的值」的意思是，即使傳入的值多於函式所需參數，`arguments` 還是會接收所有傳入的值；如果傳入的值少於函式所需參數，也不會出現 `undefined` 的值。

傳入的值多於函式所需參數：
![傳入的值多於函式所需參數](https://hackmd.io/_uploads/HyUopGo62.png)

傳入的值少於函式所需參數：
![傳入的值少於函式所需參數](https://hackmd.io/_uploads/r1avRMoah.png)


### `arguments` vs. 陣列
需要注意的是，「類陣列」只是長得很像陣列，實際上並不是真的陣列，因此不能使用 Javascript 陣列內建的操作方法。

```javascript
function fn(a,b,c){
    return arguments.sort()
}
fn(1,2,3)
// Uncaught TypeError: arguments.sort is not a function
```
`sort` 是陣列的一個方法，但 `arguments` 並非陣列，因此使用 `arguments.sort` 會報錯。

總結 `arguments` 和 `array` 的相同／相異之處：
- 都有以 `0` 為開始的索引值
- 都有 `length` 屬性可以用
- `agruments` 比陣列多了一個 `callee` 屬性：表示目前執行在哪個函式內
- `arguments` 並不是陣列，不能使用陣列的操作方法（像是 `forEach`, `map` ...等）


### 將 `arguments` 轉為陣列
雖然 `arguments` 並不是陣列，我們仍然可以透過一些方法將 `arguments` 轉為陣列來使用陣列的操作方法。

#### 使用 `Array.from()` 將 `arguments` 轉為陣列
ES6 提供了 `Array.from()` 方法，讓我們可以將物件轉為陣列：
```javascript
function accumulate() {
    console.log(arguments)
    let numbers = Array.from(arguments)
    return numbers.reduce((accum, num) => {
        return accum + num
    })
}
accumulate(1,2,3)
```
![](https://hackmd.io/_uploads/H1IXXmj62.png)

#### 使用其餘參數將 `arguments` 轉為陣列
如同上面介紹到關於其餘參數所提到的，如果函式的（最後一個）命名參數是以 `...` 開頭，引數物件便會被視為一個陣列，此時便可以使用陣列的所有操作方法。

```javascript
function accumulate(...args) {
    console.log(args)
    return args.reduce((accum, num) => {
        return accum + num
    })
}

accumulate(4,5,6)
```
![](https://hackmd.io/_uploads/HkMHn7o63.png)


#### 使用 `call()` 將 `arguments` 轉為陣列
使用 `call` 函式將 `this` 指向 `arguments` 物件：
```javascript
function accumulate() {
    const argsArray = Array.prototype.slice.call(arguments)
    return argsArray.sort()
}

accumulate(3,2,1) // [1,2,3]
```

上面的程式碼中，我們將 [`slice`](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) 函式所指向的 `this` 透過 [`call`](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Global_Objects/Function/call) 函式來指向 `arguments` 物件；也可以看作將 `arguments` 物件當作 `this` 來呼叫 `slice` 函式。

由於 `slice` 函式沒有輸入引數，將回傳原陣列，所以 `argsArray` 是陣列，含有 3 個元素。

:::info
關於 Javascript 的 `this` 以及使用 `call()` 改變 `this` 指向，可以參考先前整理的筆記：[Javascript - this 是誰、指向哪裡，以及 call、apply、bind](https://hackmd.io/KLkyl-z_SCeukUzO4R16oQ)
:::


### 引數的傳值(by value)與傳址(by reference)

先來看看這個例子：
```javascript
// 定義變數 (primitive)
let myName = 'Alice'

function fn(myName) {
    // 在函式內改變值
    myName = 'Bob'
    console.log(myName)
}

// 呼叫函式並帶入變數
fn(myName) // Bob
console.log(myName) // Alice --> 外部變數的值沒有改變
```
上面這個例子中，我們：
1. 定義了一個原始資料型別(primitive type)的變數 `myName`
2. 定義了一個函式 `fn` 及其參數 `myName`（也可以命名為任意的名字） ，並在函式內改變了參數 `myName` 的值
3. 呼叫函式並帶入引數 `fn(myName)` 並 `console.log` 變數 `myName`

可以看到即使在函式內改變 arguments，也不會改變函式外部的變數的值。


再來看看這個例子：
```javascript
// 定義變數 (object)
let myObj = {
    name: 'Alice',
    age: 18,
    isMale: false
}

function fn(myObj) {
    // 在函式內改變值
    myObj.name = 'Bob'
    console.log(myObj.name)
}

// 呼叫函式並帶入變數
fn(myObj) // 'Bob'
console.log(myObj.name) // 'Bob' --> 外部變數的值改變了！
```
但在這個例子中，外部變數 `myObj` 的值卻被函式內部改變了。

當我們在傳入引數時，需要注意函式傳入的引數是傳值(passed by value)還是傳址(passed by reference)。

#### 基本型別傳值 (Arguments are Passed by Value)
如果傳入的 arguments 的值為基本型別的值(Primitive type)，像是 `null`, `undefined`, `boolean`, `number`, `string`...，則為「傳值(pass by value)」，那麼在函式中改變 arguments 的值並不會影響函式外的變數的值。

#### 物件型別傳址(Objects are Passed by Reference)
如果傳入的 arguments 的值為物件型別的值(Object type)，像是 `object`、`array`、`function`⋯，因為物件型別是以「傳址(pass by reference)」的方式傳遞，傳入函式的是該物件型別的變數的「記憶體參考位址」，因此在函式內改變了物件的值，函式外部的物件也會被改變。

只有當物件被**賦予新的值**的時候，才會再建立一個新的記憶體位置，此時就不再指向同一個位置：
```javascript
// 定義變數 (object)
let myObj = {
    name: 'Alice',
    age: 18,
}

function fn(myObj) {
    // 賦予新值，引數 `myObj` 將指向新的記憶體位置，不再影響外部變數 `myObj`
    myObj = {
        name: 'Bob'
    }
    console.log(myObj)
}
// 呼叫函式並帶入變數
fn(myObj) // { name: 'Bob'}
console.log(myObj) // {name: 'Alice', age: 18}
```

:::info
[Javascript 中的傳值 by value 與傳址 by reference](https://medium.com/itsems-frontend/javascript-pass-by-value-reference-sharing-5d6095ae030b)
:::

## Ref
- [[JavaScript] 函式中的參數 (parameters), 引數 (arguments), 預設值 (default parameters), 和其餘運算子 (rest operator)](https://medium.com/itsems-frontend/javascript-parameters-and-arguments-5844261fe6cd)
- [call函式 & arguments物件](https://ithelp.ithome.com.tw/articles/10207798)