你真的会用扩展运算符吗?

237 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第21天,点击查看活动详情

扩展运算符,或者展开运算符,江湖人称,三个点(...),用于将可迭代对象展开到其单独的元素中,例如:数组字符串数组对象Map 、Set 、Nodelist等。

扩展运算符可以将一个数组转为用逗号分隔的参数序列

console.log(1, ...[2, 3, 4], 5) 
// 1 2 3 4 5

1. 将字符串转为字符数组

console.log([...'hello'])
// ['h', 'e', 'l', 'l', 'o']

2. 将NodeList转为数组

NodeList 对象是DOM节点的集合,它类似于数组,但不是数组,可以使用 forEach() 和for..of...来遍历,但没有findmapfilter 等数组方法。

通过扩展运算符可以将其转为数组,以获取掘金首页元素为例:

const nodeList = document.querySelectorAll('.info-box')
console.log(nodeList)
const nodeArr = [...nodeList]
console.log(nodeArr)

image.png

3. 函数传参

function add(x, y) {
  return x + y;
}

const numbers = [10, 21];
add(...numbers) // 31

注意:只有函数调用时,扩展运算符才可以放在圆括号中,否则会报错

(...[1, 2])
// Uncaught SyntaxError: Unexpected number

console.log((...[1, 2]))
// Uncaught SyntaxError: Unexpected number

console.log(...[1, 2])
// 1 2

4. 替代apply方法

ES5提供的将数组转换为函数参数的方法是使用apply()

// ES5 的写法
function f(x, y, z) {
  // ...
}
var args = [0, 1, 2];
f.apply(null, args);

比如JavaScript不提供求数组最大元素的函数,只能将数组转为一个参数序列,然后使用Math.max()求最大值

// ES5 的写法
Math.max.apply(null, [14, 3, 77])
// 等同于
Math.max(14, 3, 77);

有了扩展运算符以后,就可以直接用Math.max()了:

// ES6 的写法
Math.max(...[14, 3, 77])
// 等同于
Math.max(14, 3, 77);

5. 复制数组

复制一层深拷贝

const arr1 = [1, 2, 3]; 

// 写法1
const arr2 = [...arr1]; 
// 写法2
const [...arr2] = [arr1]

arr2.push(4);

console.log(arr1) // [1, 2, 3]
console.log(arr2) // [1, 2, 3, 4]

注意:数组的空位会用undefined来补上

let arr1 = [1, , 3]
arr2 = [...arr3]

console.log(arr2); // [1, undefined, 3]

复制两层及以上浅拷贝

const arr1 = [[1], 2, 3];
const arr2 = [...arr1];

arr2[0].push(4);

console.log(arr1); // [ [ 1, 4 ], 2, 3 ]
console.log(arr2); // [ [ 1, 4 ], 2, 3 ]

6. 合并数组

const arr1 = [1, 2];
const arr2 = [3, 4];
const arr3 = [...arr1, ...arr2]

console.log(arr3) // [ 1, 2, 3, 4]

7. 分割数组

扩展运算符可以与解构赋值结合来切割原先数组,从而用于生成新的数组

const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first) // 1
console.log(rest)  // [2, 3, 4, 5]

const [first, ...rest] = [];
console.log(first) // undefined
console.log(rest)  // []

const [first, ...rest] = ["foo"];
console.log(first)  // "foo"
console.log(rest)   // []

注意:只能将扩展运算符放在最后

const [...butLast, last] = [1, 2, 3, 4, 5];
// SyntaxError: Rest element must be last element
const [first, ...middle, last] = [1, 2, 3, 4, 5];
// SyntaxError: Rest element must be last element

8. 数组去重

与 Set 一起使用消除数组的重复项:

const arr = [1, 2, 3, 4, 5, 2, 5]; 
const newArr = [...new Set(arr)]; 

console.log(newArr); // [ 1, 2, 3, 4, 5 ]

对象的扩展运算符,只会返回参数对象自身的、可枚举的属性

class C {
  p = 12;
  m() {}
}

let c = new C();
let clone = { ...c };

clone.p; // ok
clone.m(); // 报错

如果扩展运算符后面不是对象,则会自动将其转为对象:

{...1} // {}
{...true} // {}
{...undefined} // {}
{...null} // {}

如果扩展运算符后面是一个空对象,则没有任何效果:

{...{}, a: 1} // { a: 1 }

如果扩展运算符后面是字符串,它会自动转成一个类似数组的对象:

{...'hello'} // {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}

由于数组是特殊的对象,所以对象的扩展运算符也可以用于数组:

{ ...['a', 'b', 'c'] }; // {0: "a", 1: "b", 2: "c"}

9. 复制对象

等同于使用Object.assign()方法

复制一层深拷贝

let obj1 = { a: 1, b: 2 };
let obj2 = { ...obj1 }; // 等同于 let obj2 = Object.assign({}, obj1);
obj2.a = 3;

console.log(obj1); // { a: 1, b: 2 }
console.log(obj2); // { a: 3, b: 2 }

复制两层及以上浅拷贝

let obj1 = { a: { b: 1 }, c: 2 };
let obj2 = { ...obj1 }; // 等同于 let obj2 = Object.assign({}, obj1);

obj2.a.b = 3;

console.log(obj1); // { a: { b: 3 }, c: 2 }
console.log(obj2); // { a: { b: 3 }, c: 2 }

10. 合并对象

let obj1 = { a: 1 };
let obj2 = { b: 2 };

let obj3 = {...obj1, ...obj2} // 等同于let obj3 = Object.assign({}, obj1, obj2);

console.log(obj3); //  { a: 1, b: 2 }

如果用户自定义的属性,放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉:

let obj1 = { a: 1 };
let obj2 = { b: 2 };

let obj3 = { ...obj1, ...obj2, a: 3, c: 4 };

console.log(obj3); // { a: 3, b: 2, c: 4 }

如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值,同名属性会被扩展运算符中的覆盖掉:

let obj1 = { a: 1 };
let obj2 = { b: 2 };

let obj3 = { a: 3, c: 4, ...obj1, ...obj2 };

console.log(obj3); // { a: 1, c: 4, b: 2 }