ES6"..."扩展运算符

在数组中的应用

扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,++将一个数组转为用逗号分隔的参数序列++。

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

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

[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]

扩展运算符与正常的函数参数可以结合使用,非常灵活。

function f(v, w, x, y, z) { }
const args = [0, 1];
f(-1, ...args, 2, ...[3]);

// ES6的写法
function f(x, y, z) {
  // ...
}
let args = [0, 1, 2];
f(...args);

扩展运算符后面还可以放置表达式。

const arr = [
  ...(x > 0 ? ['a'] : []),
  'b',
];

通过push函数,将一个数组添加到另一个数组的尾部。

ES5 写法中,push方法的参数不能是数组,所以只好通过apply方法变通使用push方法。有了扩展运算符,就可以直接将数组传入push方法。

// ES5的 写法
var arr1 = [0, 1, 2];
var arr2 = [3, 4, 5];
Array.prototype.push.apply(arr1, arr2);

// ES6 的写法
let arr1 = [0, 1, 2];
let arr2 = [3, 4, 5];
arr1.push(...arr2);

通过扩展运算符,生成日期

// ES5
new (Date.bind.apply(Date, [null, 2015, 1, 1]))
// ES6
new Date(...[2015, 1, 1]);

复制数组

a1会返回原数组的克隆,再修改a2就不会对a1产生影响。

//es5
const a1 = [1, 2];
const a2 = a1.concat();
a2[0] = 2;
a1 // [1, 2]

//es6
const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;

合并数组

这两种方法都是浅拷贝

const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];

// ES5 的合并数组
arr1.concat(arr2, arr3);
// [ 'a', 'b', 'c', 'd', 'e' ]

// ES6 的合并数组
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]

与解构赋值结合

如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错。

const [first, ...rest] = [1, 2, 3, 4, 5];
first // 1
rest  // [2, 3, 4, 5]

const [first, ...rest] = [];
first // undefined
rest  // []

const [first, ...rest] = ["foo"];
first  // "foo"
rest   // []

字符串

扩展运算符还可以将字符串转为真正的数组。

[...'hello']
// [ "h", "e", "l", "l", "o" ]

实现了 Iterator 接口的对象

querySelectorAll方法返回的是一个NodeList对象。它不是数组,而是一个类似数组的对象。这时,扩展运算符可以将其转为真正的数组,原因就在于NodeList对象实现了 Iterator 。

let nodeList = document.querySelectorAll('div');
let array = [...nodeList];

Map 和 Set 结构,Generator 函数

扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符,比如 Map 结构。

let map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);

let arr = [...map.keys()]; // [1, 2, 3]

对象的扩展

解构赋值

变量z是解构赋值所在的对象。它获取等号右边的所有尚未读取的键(a和b),将它们连同值一起拷贝过来。

let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }

解构赋值必须是最后一个参数,否则会报错。

let { ...x, y, z } = someObject; // 句法错误
let { x, ...y, ...z } = someObject; // 句法错误

解构赋值的拷贝是浅拷贝,解构赋值拷贝的是这个值的引用

let obj = { a: { b: 1 } };
let { ...x } = obj;
obj.a.b = 2;
x.a.b // 2

拷贝对象

let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }

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

合并两个对象

let ab = { ...a, ...b };
// 等同于
let ab = Object.assign({}, a, b);

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

a对象的x属性和y属性,拷贝到新对象后会被覆盖掉。

let aWithOverrides = { ...a, x: 1, y: 2 };
// 等同于
let aWithOverrides = { ...a, ...{ x: 1, y: 2 } };
// 等同于
let x = 1, y = 2, aWithOverrides = { ...a, x, y };
// 等同于
let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });

修改现有对象部分的属性

let newVersion = {
  ...previousVersion,
  name: 'New Name' // Override the name property
};

如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。

let aWithDefaults = { x: 1, y: 2, ...a };
// 等同于
let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a);
// 等同于
let aWithDefaults = Object.assign({ x: 1, y: 2 }, a);
posted @ 2020-02-07 11:33  未月廿三  阅读(408)  评论(0编辑  收藏  举报