扩展运算符,...
,最早是在 ES6(ES2015)引入的,仅用于数组,它很受欢迎,在 ES9(ES2018)为对象引入了对应的特性。本文介绍扩展运算符的实际应用。
拷贝数组或对象
当我们需要修改数组或对象,但又不希望改变原有数组或对象时,就需要拷贝一份数据:
拷贝数组
浅拷贝(Shallow-cloning)数组时,可以使用更简短的展开语法。
1
2
3
4
5
6
7
let arr = [1, 2, 3];
let arr2 = [...arr]; // like arr.slice(); or arr.map(a => a)
arr2.push(4);
console.log(arr); // [1, 2, 3]
console.log(arr2); // [1, 2, 3, 4]
拷贝对象
浅拷贝(Shallow-cloning, 不包含 prototype)对象时,可以使用更简短的展开语法,而不必使用 Object.assign()
方式。
1
2
3
4
5
6
7
let obj = { foo: 'bar', x: 42 };
let clonedObj = { ...obj }; // like Object.assign({}, obj);
clonedObj.x = 34;
console.log(clonedObj); // {foo: "bar", x: 34}
console.log(obj); // {foo: "bar", x: 42}
如果您需要深度拷贝,推荐使用外部库(如:Lodash),或自己编写一个函数来完成,下面这种方式也是一种解决方案,不过不推荐使用。
1 2 3 4 5 6 7 8 let obj = { foo: 'bar', x: 42, y: [1, 2] }; let clonedObj = { ...obj, y: [...obj.y] }; clonedObj.x = 34; clonedObj.y.push(44); console.log(clonedObj); // { foo: "bar", x: 34, y: Array [1, 2, 44] } console.log(obj); // { foo: "bar", x: 42, y: Array [1, 2] }
合并数组或对象
合并数组
1
2
3
4
5
6
let a = [1, 2, 3];
let b = [4, 5, 6];
let mergeab = [...a, ...b]; // like a.concat(b); or a.push.apply(a, b);
console.log(mergeab); // [1, 2, 3, 4, 5, 6]
合并对象
1
2
3
4
5
6
let obj1 = { foo: 'bar', x: 42 };
let obj2 = { foo: 'baz', y: 13 };
let mergedObj = {...obj1, ...obj2}; // like Object.assign({}, obj1, obj2);
console.log(mergedObj); // {foo: "baz", x: 42, y: 13}
向数组或对象中添加元素
向数组中添加元素
1
2
3
4
let a = [1, 2, 3];
let b = [...a, 4, 5, 6];
console.log(b); // [1, 2, 3, 4, 5, 6]
向对象中添加元素
1
2
3
4
5
6
7
8
9
10
11
let person = {
name: 'songzhenzhong',
age: 8
}
let person2 = {
...person,
birthday: '1941',
death_date: '1949-09-06'
}
console.log(person2); // {name: "songzhenzhong", age: 8, birthday: "1941", death_date: "1949-09-06"}
正如您所看到的,我们可以直接在对象字面量内部声明和初始化属性,而不是在外面。
唯一数组
如果我们想从数组中筛选出重复的元素,那么最简单的解决方案是什么?
Set
对象仅存储唯一的元素,并且可以用数组填充。它也是可迭代的,因此我们可以将其展开到新的数组中,并且得到的数组中的值是唯一的。
1
2
3
4
let a = [1, 2, 2, 3, 4, 4, 5];
let b = [...new Set(a)]; // like a.filter((currentValue, index, arr) => arr.indexOf(currentValue) === index);
console.log(b); // [1, 2, 3, 4, 5]
将类数组(array-like)结构转换为数组
类数组结构与数组非常相似,它们通常具有编号元素(key)和长度属性。然而,它们有一个重要的区别,类数组结构没有任何数组函数。
使用与拷贝数组相同的语法,我们可以使用扩展运算符将类数组结构转换为数组,作为使用 Array.from()
的替代方法。看一个将 nodeList 转换为数组的示例:
1
2
3
4
5
let nodeList = document.getElementsByClassName("moon");
let array = [...nodeList]; // like Array.prototype.slice(nodeList);
console.log(nodeList); //Result: HTMLCollection [ div.moon, div.moon ]
console.log(array); //Result: Array [ div.moon, div.moon ]
使用这种技术,我们可以将任何类似数组的结构转换为数组,从而可以访问所有数组函数。
再例如将遍历器转为数组:
1
2
3
4
let string = 'test1test2test3';
let regex = /t(e)(st(\d?))/g;
let result = [...string.matchAll(regex)];
将参数作为数组进行传递
1
2
3
4
let numbers = [1, 4, 5, 6, 9, 2, 3, 4, 5, 6];
let max = Math.max(...numbers); // like Math.max.apply(null, numbers);
console.log(max); // 9
将字符串拆分为字符
1
2
3
4
5
let text = 'The best time';
let textArray = [...text]; // like text.split('');
console.log(textArray); // ["T", "h", "e", " ", "b", "e", "s", "t", " ", "t", "i", "m", "e"]
剩余参数
函数的最后一个参数可以以 ...
为前缀,这将导致所有剩余的(用户提供的)参数放置在标准 javascript 数组中。只有最后一个参数可以是剩余参数。
1
2
3
4
5
6
7
8
9
10
11
12
function myFun(a, b, ...manyMoreArgs) {
console.log("a", a)
console.log("b", b)
console.log("manyMoreArgs", manyMoreArgs)
}
myFun("one", "two", "three", "four", "five", "six")
// Console Output:
// a, one
// b, two
// manyMoreArgs, ["three", "four", "five", "six"]
解构时也可以使用剩余参数,它允许我们获取对象中剩余的属性,并存储它们。
1
2
3
4
5
6
7
8
let pokemon = {
id: 1,
name: 'Squirtle',
type: 'Water'
};
let { id, ...rest } = pokemon;
console.log(rest); // { name: 'Squirtle', type: 'Water' }
数组同理,数组切片应用:
1
2
3
4
let a = [1, 2, 3];
[one, ...b] = a; // like b = a.slice(1);
console.log(b); // [2, 3]
剩余参数可以被解构,这意味着它们的数据可以被解包到不同的变量中。
1
2
3
4
5
6
7
function f(...[a, b, c]) {
return a + b + c;
}
f(1) // NaN (b and c are undefined)
f(1, 2, 3) // 6
f(1, 2, 3, 4) // 6 (the fourth parameter is not destructured)
将 arguments
对象转为数组
剩余参数通常用于将一组参数转换为数组。
剩余参数和 arguments
对象的区别
- 剩余参数只包含那些没有对应形参的实参,而
arguments
对象包含了传给函数的所有实参。 arguments
对象不是一个真正的数组,而剩余参数是真正的Array
实例,也就是说你能够在它上面直接使用所有的数组方法,比如sort
,map
,forEach
或pop
。arguments
对象还有一些附加的属性 (如callee
属性)。
1
2
3
4
function f(...args) {
let normalArray = args
let first = normalArray.shift() // OK, gives the first argument
}
在 arguments
对象上使用 Array
方法
1
2
3
4
5
6
7
8
9
10
function f(a, b) {
let normalArray = Array.prototype.slice.call(arguments)
// -- or --
let normalArray = [].slice.call(arguments)
// -- or --
let normalArray = Array.from(arguments)
let first = normalArray.shift() // OK, gives the first argument
let first = arguments.shift() // ERROR (arguments is not a normal array)
}
参考文章
- Spread syntax (…)
- Rest parameters
- Understanding the JavaScript Spread Operator — From Beginner to Expert
- Understanding the JavaScript Spread Operator — Advanced Uses
- Javascript 中的 …(展开运算符)