owned this note
owned this note
Published
Linked with GitHub
# 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)