许多 JavaScript 内置函数支持任意数量的参数。
例如
Math.max(arg1, arg2, ..., argN)
– 返回参数中的最大值。Object.assign(dest, src1, ..., srcN)
– 将src1..N
中的属性复制到dest
中。- ……等等。
在本章中,我们将学习如何执行相同操作。此外,还将学习如何将数组作为参数传递给此类函数。
剩余参数 ...
无论如何定义函数,都可以使用任意数量的参数调用该函数。
如下所示
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");
剩余参数收集所有剩余参数,因此以下内容没有意义并会导致错误
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");
在过去,该语言中不存在剩余参数,而使用 arguments
是获取函数所有参数的唯一方法。它仍然有效,我们可以在旧代码中找到它。
但缺点是,尽管 arguments
既类似数组又可迭代,但它不是数组。它不支持数组方法,因此我们无法调用 arguments.map(...)
。
此外,它始终包含所有参数。我们无法部分捕获它们,就像我们使用剩余参数所做的那样。
因此,当我们需要这些特性时,则优先使用剩余参数。
"arguments"
如果我们从箭头函数访问 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])
中手动列出项,因为我们可能不确定有多少项。当我们的脚本执行时,可能有很多,也可能没有。那会变得很丑陋。
展开语法 来救援!它看起来类似于剩余参数,也使用 ...
,但恰恰相反。
当在函数调用中使用 ...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)
短得多,因此我们希望尽可能使用它。
总结
当我们在代码中看到 "..."
时,它要么是剩余参数,要么是展开语法。
有一种简单的方法可以区分它们
- 当
...
位于函数参数的末尾时,它就是“剩余参数”,并将参数列表的其余部分收集到数组中。 - 当
...
出现在函数调用或类似位置时,它被称为“展开语法”,并将数组展开为列表。
使用模式
- 剩余参数用于创建接受任意数量参数的函数。
- 展开语法用于将数组传递给通常需要多个参数列表的函数。
它们共同帮助轻松地在列表和参数数组之间进行转换。
函数调用的所有参数也存在于“旧式”arguments
:类数组可迭代对象中。
评论
<code>
标记,对于多行代码,请用<pre>
标记将其包装起来,对于超过 10 行的代码,请使用沙盒 (plnkr、jsbin、codepen…)