許多 JavaScript 內建函式支援任意數量的參數。
例如
Math.max(arg1, arg2, ..., argN)
– 傳回參數中最大的值。Object.assign(dest, src1, ..., srcN)
– 將屬性從src1..N
複製到dest
中。- …以此類推。
在本章節中,我們將學習如何執行相同的操作。此外,我們還將學習如何將陣列傳遞給函式作為參數。
Rest 參數 ...
函式可以呼叫任何數量的引數,無論其定義方式為何。
如下所示
function sum(a, b) {
return a + b;
}
alert( sum(1, 2, 3, 4, 5) );
不會因為「過多」的引數而發生錯誤。但當然在結果中,只會計算前兩個引數,因此上述程式碼中的結果為 3
。
可以使用三個點 ...
後接將包含這些引數的陣列名稱,將其餘的參數包含在函式定義中。這些點字面上的意思為「將其餘的參數收集到陣列中」。
例如,要將所有引數收集到陣列 args
中
function sumAll(...args) { // args is the name for the array
let sum = 0;
for (let arg of args) sum += arg;
return sum;
}
alert( sumAll(1) ); // 1
alert( sumAll(1, 2) ); // 3
alert( sumAll(1, 2, 3) ); // 6
我們可以選擇將前幾個參數作為變數取得,並只收集其餘的參數。
在此,前兩個引數會進入變數中,其餘的會進入 titles
陣列中
function showName(firstName, lastName, ...titles) {
alert( firstName + ' ' + lastName ); // Julius Caesar
// the rest go into titles array
// i.e. titles = ["Consul", "Imperator"]
alert( titles[0] ); // Consul
alert( titles[1] ); // Imperator
alert( titles.length ); // 2
}
showName("Julius", "Caesar", "Consul", "Imperator");
Rest 參數會收集所有其餘的引數,因此下列程式碼沒有意義,而且會導致錯誤
function f(arg1, ...rest, arg2) { // arg2 after ...rest ?!
// error
}
...rest
必須永遠是最後一個。
「arguments」變數
還有一個特殊的類陣列物件稱為 arguments
,其中包含所有引數及其索引。
例如
function showName() {
alert( arguments.length );
alert( arguments[0] );
alert( arguments[1] );
// it's iterable
// for(let arg of arguments) alert(arg);
}
// shows: 2, Julius, Caesar
showName("Julius", "Caesar");
// shows: 1, Ilya, undefined (no second argument)
showName("Ilya");
在以前,語言中沒有 Rest 參數,而使用 arguments
是取得函式所有引數的唯一方法。而且它仍然有效,我們可以在舊程式碼中找到它。
但缺點是,儘管 arguments
類似陣列且可迭代,但它並非陣列。它不支援陣列方法,因此我們無法呼叫 arguments.map(...)
。
此外,它總是包含所有引數。我們無法像使用 Rest 參數那樣部分擷取它們。
因此,當我們需要這些功能時,建議使用 Rest 參數。
"arguments"
如果我們從 Arrow 函式存取 arguments
物件,它會從外部的「一般」函式中取得它們。
以下是一個範例
function f() {
let showArg = () => alert(arguments[0]);
showArg();
}
f(1); // 1
如我們所知,箭頭函式沒有自己的 this
。現在我們知道它們也沒有特殊的 arguments
物件。
展開語法
我們剛剛看過如何從參數清單中取得陣列。
但有時我們需要做完全相反的事。
例如,有一個內建函式 Math.max,它會傳回清單中最大的數字
alert( Math.max(3, 5, 1) ); // 5
現在假設我們有一個陣列 [3, 5, 1]
。我們要如何用它呼叫 Math.max
?
「原樣」傳遞它不會起作用,因為 Math.max
預期的是一個數字引數清單,而不是單一陣列
let arr = [3, 5, 1];
alert( Math.max(arr) ); // NaN
而且我們當然不能在程式碼 Math.max(arr[0], arr[1], arr[2])
中手動列出項目,因為我們可能不確定有多少個項目。當我們的指令碼執行時,可能會有很多,也可能沒有。那樣會很醜陋。
展開語法 來救援!它看起來類似於 rest 參數,也使用 ...
,但做的是完全相反的事。
當在函式呼叫中使用 ...arr
時,它會將可迭代物件 arr
「展開」成引數清單。
對於 Math.max
let arr = [3, 5, 1];
alert( Math.max(...arr) ); // 5 (spread turns array into a list of arguments)
我們也可以用這種方式傳遞多個可迭代物件
let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];
alert( Math.max(...arr1, ...arr2) ); // 8
我們甚至可以將展開語法與一般值結合使用
let arr1 = [1, -2, 3, 4];
let arr2 = [8, 3, -8, 1];
alert( Math.max(1, ...arr1, 2, ...arr2, 25) ); // 25
此外,展開語法可以用於合併陣列
let arr = [3, 5, 1];
let arr2 = [8, 9, 15];
let merged = [0, ...arr, 2, ...arr2];
alert(merged); // 0,3,5,1,2,8,9,15 (0, then arr, then 2, then arr2)
在上面的範例中,我們使用陣列來示範展開語法,但任何可迭代物件都可以。
例如,這裡我們使用展開語法將字串轉換成字元陣列
let str = "Hello";
alert( [...str] ); // H,e,l,l,o
展開語法在內部使用迭代器來收集元素,就像 for..of
所做的一樣。
因此,對於字串,for..of
會傳回字元,而 ...str
會變成 "H","e","l","l","o"
。字元清單會傳遞給陣列初始化器 [...str]
。
對於這個特定任務,我們也可以使用 Array.from
,因為它會將可迭代物件(例如字串)轉換成陣列
let str = "Hello";
// Array.from converts an iterable into an array
alert( Array.from(str) ); // H,e,l,l,o
結果與 [...str]
相同。
但 Array.from(obj)
和 [...obj]
之間有一個細微的差別
Array.from
對類陣列和可迭代物件都有效。- 展開語法只對可迭代物件有效。
因此,對於將某個東西轉換成陣列的任務,Array.from
往往更通用。
複製陣列/物件
還記得我們以前討論過 Object.assign()
嗎?
使用展開語法也可以做到同樣的事情。
let arr = [1, 2, 3];
let arrCopy = [...arr]; // spread the array into a list of parameters
// then put the result into a new array
// do the arrays have the same contents?
alert(JSON.stringify(arr) === JSON.stringify(arrCopy)); // true
// are the arrays equal?
alert(arr === arrCopy); // false (not same reference)
// modifying our initial array does not modify the copy:
arr.push(4);
alert(arr); // 1, 2, 3, 4
alert(arrCopy); // 1, 2, 3
請注意,可以對物件做同樣的事情來製作副本
let obj = { a: 1, b: 2, c: 3 };
let objCopy = { ...obj }; // spread the object into a list of parameters
// then return the result in a new object
// do the objects have the same contents?
alert(JSON.stringify(obj) === JSON.stringify(objCopy)); // true
// are the objects equal?
alert(obj === objCopy); // false (not same reference)
// modifying our initial object does not modify the copy:
obj.d = 4;
alert(JSON.stringify(obj)); // {"a":1,"b":2,"c":3,"d":4}
alert(JSON.stringify(objCopy)); // {"a":1,"b":2,"c":3}
這種複製物件的方式比 let objCopy = Object.assign({}, obj)
或陣列的 let arrCopy = Object.assign([], arr)
短得多,所以我們盡量在可以的時候使用它。
摘要
當我們在程式碼中看到 "..."
時,它可能是 rest 參數或展開語法。
區分它們的方法很簡單
- 當
...
出現在函式參數的結尾時,它就是「rest 參數」,它會將參數清單的其餘部分收集到陣列中。 - 當
...
出現在函式呼叫或類似的地方時,它稱為「展開語法」,它會將陣列展開成清單。
使用模式
- Rest 參數用於建立可接受任意數目參數的函式。
- 展開語法用於將陣列傳遞給通常需要許多參數清單的函式。
它們一起協助輕鬆地在清單和參數陣列之間移動。
函式呼叫的所有參數也可用於「舊式」arguments
:類似陣列的可迭代物件。
留言
<code>
標籤,若要插入多行程式碼,請將它們包覆在<pre>
標籤中,若要插入超過 10 行的程式碼,請使用沙盒 (plnkr、jsbin、codepen…)