javascript weird part (31) – argument 與 spread

這一節是是關於另一個 JavaScript 的關鍵字「參數 (arguments)」,這是在執行一個函式的時候引擎會自動幫我們設定好的關鍵字。雖然在下一版本的 JavaScript 就不會這麼常提到這個關鍵字,而是用 spread 處理 arguments 的工作。但是如果現在去看任何程式碼,尤其是框架和資源庫,還是會在原始碼看到 arguments 變數。所以我們應該要認識及知道它的用處。

已知執行函數的時候,一個執行環境被創造,然後 JavaScript 引擎會幫你設定一些東西,像是變數環境 來包住變數,給範圍鏈的外部環境參考,以及特殊關鍵字 this (它會依據函數的位置和不同被呼叫的方式,指向不同的東西)。最後它還設定一個特殊關鍵字 arguments
arguments 包含所有的值,所有傳入所呼叫的函數的參數 (the parameters you pass into a function) 。其實 arguments 只是另一個傳入函數的參數的名稱而已,但是 JavaScript 用 arguments 統稱所有「傳入函數的參數」。

arguments

要了解 arguments 到底用處是什麼,可以看以下例子

1
2
3
4
5
6
7
8
9
10
11
12
function greet(firstname, lastname, language) {



console.log(firstname);
console.log(lastname);
console.log(language);


}

greet();

JavaScript 和其它程式語言的差別在於,我可以呼叫 greet 然後不傳入任何參數,而不會報錯。在瀏覽器執行,得到 undefined。在函數執行的時候,發生提昇 (hoisting) 的狀況,它設定好這些東西的初始值,即便我還沒提供確切的特定值。執行函數的地一件事情是設定好 firstnamelastname 的記憶體空間,然後設定它們為 undefined。

如果傳入參數,它會由左到右處理,如果我只傳入一個 ‘John’,它會假定這是 firstname 然後我沒有傳入其它東西到 lastnamelanguage,出現 undefined 的原因就是因為 hoisting。這表示可以省略傳入參數,或者可以只傳入一部份的參數

1
2
3
4
5
6
7
8
9
10
11
12
function greet(firstname, lastname, language) {
console.log(firstname);
console.log(lastname);
console.log(language);
console.log('-------------');

}

greet();
greet('John');
greet('John', 'Doe');
greet('John', 'Doe', 'es');

![](https://i.imgur.com/INhLkRQ.

雖然這看起來有點奇怪,但其實這是一個強大的概念。例如,在下一版本的 JavaScript ,如果沒有傳入參數值到函數裡面,可以設定預定參數

1
2
3
4
5
6
7
8
9
10
11
12
function greet(firstname, lastname, language='en') {
console.log(firstname);
console.log(lastname);
console.log(language);
console.log('-------------');

}

greet();
greet('John');
greet('John', 'Doe');
greet('John', 'Doe', 'es');

然而,這並非所有瀏覽器都支援,所以可以這樣使用預定參數的概念。亦即如果沒有傳入參數給函式,如果是 undefined 就會使用 || 運算子,強制型轉成 false,因為 undefine 會強制型轉成 false,最終會得到 en 的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function greet(firstname, lastname, language) {

language = language || 'en';
console.log(firstname);
console.log(lastname);
console.log(language);
console.log('-------------');

}

greet();
greet('John');
greet('John', 'Doe');
greet('John', 'Doe', 'es');

![](https://i.imgur.com/iUGjoEh.

現在繼續關注 JavaScript 設定的關鍵字 arguments ,沒有在任何地方宣告,但自動就可以取用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function greet(firstname, lastname, language) {

language = language || 'en';
console.log(firstname);
console.log(lastname);
console.log(language);
console.log(arguments);
console.log('-------------');

}

greet();
greet('John');
greet('John', 'Doe');
greet('John', 'Doe', 'es');

argument 包含所有傳入參數的值,但因為這是一個特殊的關鍵字,所以那些微微傾斜的中括號,意味它其實是 「像陣列的」(array-like)。這表示它的動作和陣列相似,看起來也很像。但它不是陣列。它只有一部份陣列的功能。

所以如果我不想要我的函式做任何動作,也沒有傳入任何的參數,我可以檢查 argument.length,長度為 0 表示這個陣列是空的,一個回傳陳述句會將我踢出函式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function greet(firstname, lastname, language) {

language = language || 'en';

if (arguments.length === 0) {
console.log('Missing parameters!');
console.log('-------------');
return;
}

console.log(firstname);
console.log(lastname);
console.log(language);
console.log(arguments);
console.log('-------------');

}

greet();
greet('John');
greet('John', 'Doe');
greet('John', 'Doe', 'es');

所以雖然 argument 沒有參數名稱,只有值,我依然可以像陣列一樣使用它,可以用中括號運算子,像是我們可以利用 arguments 來判斷函式有沒有傳入參數,或者取出特定索引的值等等運用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function greet(firstname, lastname, language) {

language = language || 'en';

if (arguments.length === 0) {
console.log('Missing parameters!');
console.log('-------------');
return;
}

console.log(firstname);
console.log(lastname);
console.log(language);
console.log(arguments);
console.log('arg 0: ' + arguments[0]);
console.log('-------------');

}

greet();
greet('John');
greet('John', 'Doe');
greet('John', 'Doe', 'es');

spread parameter 其餘參數

隨著 JavaScript 的發展,在 ES6 中,我們可以使用其餘參數...來取代 arguments ,但不代表 arguments 不存在了,它仍然可以使用,只是有更好的選擇。

簡單來說如果我們有傳入函式的參數,可以使用「…」來省略,但要特別注意的是,只能在沒有其他參數下,或者「…」必須是最後一個參數才可以使用。

其餘參數比起 arguments 好用的地方在於,其餘參數是一個真正的陣列,支援所有可以用於陣列上的方法, arguments 是類陣列,處理上較為麻煩。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function greet(firstname, lastname, language,...other) {

language = language || 'en';

if (arguments.length === 0) {
console.log('Missing parameters!');
console.log('-------------');
return;
}

console.log(firstname);
console.log(lastname);
console.log(language);
console.log(arguments);
console.log('arg 0: ' + arguments[0]);
console.log('-------------');

}

greet();
greet('John');
greet('John', 'Doe');
greet('John', 'Doe', 'es','other1','other2');

計算加總  —  其餘參數

1
2
3
4
5
6
7
8
9
function sum(...input) {
var result = 0;
input.forEach(function(input) {
result += input;
});
return result;
}
console.log(sum(1)); // 1
console.log(sum(1, 2, 3, 4, 5)); // 15

計算加總  — arguments

1
2
3
4
5
6
7
8
9
function sum() {
var result = 0;
for (var i = 0;i<arguments.length;i++){
result += arguments[i]
}
return result;
}
console.log(sum(1)); // 1
console.log(sum(1, 2, 3, 4, 5)); // 15

雖然 arguments 也能做到一樣的事情,但是因為不是真正的陣列,沒辦法使用 ES6 新增的一些好用的陣列方法,所以只能使用 for 迴圈一個個加總,或者使用其他方式將類陣列轉換成真正的陣列。

也因為 arguments 不能自訂一個名稱,所以也很難讓人明白到底這段程式是在做些什麼。