ES6+ 展开语法

1. 前言

ES6 新增了 ... 的语法糖,主要用于展开语法和剩余语法中,本节先来说说展开语法。展开语法顾名思义可以理解为把整体展开成个体,在 ES5 中如果想把一个数组的内容拷贝到另一个数组中,可以使用 for 循环数组的每一项,然后添加到目标数组中去。但是如果使用展开语法就很方便地完成这个操作了。下面我们就来看看展开语法是如何使用的。

2. 从拷贝说起

2.1 数组拷贝

在 ES5 经常会遇到数组和对象的浅拷贝,我们都知道数组和对象都是引用类型,所以不能像字符串那样直接赋值,在 ES5 中数组和对象的拷贝都是通过循环来实现的,下面我们来看几个例子:

var arr1 = [1, 2, 3];
var arr2 = [];
arr1.forEach(function(value){
  arr2.push(value);
}) 
console.log(arr2);	// [1, 2, 3]

上面的代码是把 arr1 数组中的项拷贝到 arr2 中去,还可以使用数组提供的 concat 和 slice 方法来实现。

var arr1 = [1, 2, 3];
var arr2 = [].concat(arr1);
var arr3 = arr1.slice(0);
arr1.push(4)
console.log(arr1); //[1, 2, 3, 4]
console.log(arr2); //[1, 2, 3]
console.log(arr3); //[1, 2, 3]

在拷贝完后,对 arr1 数组添加元素,可以看到 arr2 和 arr3 没有发生变化,说明它们不是一个引用地址。这是 ES5 所提供的拷贝方式,那么 ES6 是如何简化的呢?

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

使用 ... 展开语法可以实现与上面 ES5 实现的相同效果,而且比较简洁地表达了把 arr1 中的每一项展开放入 arr2 中。

2.2 字面量对象拷贝

上面说到了 ES5 和 ES6 数组拷贝的一个对比,那么针对字面量对象的拷贝二者又是怎么来实现的呢?

ES5 中针对字面量对象的拷贝方式比较少,没有数组提供的类似的方法可以使用,只能使用循环,但是还可以使用 JSON.stringifyJSON.parse 来实现,但是这个方法存在一些缺陷。 下面看 ES5 中字面量的拷贝实例:

let obj = {a: 1, b: 2};
let copy1 = {};
for(let key in obj) {
  copy1[key] = obj[key] 
}
let copy2 = JSON.parse(JSON.stringify(obj))

上面的两种方法给出了 ES5 拷贝字面量对象的方法,比较麻烦,也容易出错。ES6 给出了它的答案:

let obj = {a: 1, b: 2};
let copy = {...obj};

使用展开语法对 obj 进行展开,完美地实现了拷贝过程。

Tips: 这里有必要说明一下,以上的方法都是浅拷贝(只拷贝数组和对象的第一层结构)的过程,对于数组和对象第一层以后的内容,如果是引用类型的存储方式,则不会进行拷贝操作,也就是不会进行深拷贝。

3. 语法详情

上面通过拷贝初步了解了展开语法,这里我们给出展开语法的定义:展开语法在函数调用和构造数组时,将字符串和数组在语法层面展开;如果是对象时,将对象的表达式按照 key-value 的方式展开。展开语法的使用主要有以下几种:

  • 处理字符串、数组和字面量对象;
  • 简化函数调用时传参问题;
  • 代替 apply 方法。

3.1 在字符串中的使用

展开语法在处理字符串时,顾名思义可以把字符进行展开,从而得到一个每项都是单个字符串的数组,注意展开语法在字符串使用时,需要包裹在 [] 中才能生效。

const arr = [...'imooc'];
console.log(arr); // ["i", "m", "o", "o", "c"]

在 ES5 中也有方法,可以使用 split 方法实现把字符串变成数组。

const arr = 'imooc'.split('');
console.log(arr); // ["i", "m", "o", "o", "c"]

3.2 在数组中的使用

上面我们讲了 ES5 中对一个数组的拷贝,在数组的操作中还有添加、合并等操作的时候,需要调用数组的 slice ()concat ()unshift () 等方法,或者组合使用这些方法。

const arr1 = [1, 2];
const arr2 = ['a', ...arr1];
const arr3 = [...arr1, ...arr2];

console.log(arr2);	// ['a', 1, 2]
console.log(arr3);	// [1, 2, 'a', 1, 2]

上面的代码可以看出,展开语法有很多种不同的使用方式,我们可以把展开语法当成一个整体,直接放到想放到的位置上即可,扩展了操作数组的方式。

3.3 在字面量对象中的使用

和数组一样,展开语法在字面量对象中的使用方式也有很多种:

const obj1 = {a: 1, b: 2};
const obj2 = {...obj1, c: 30};
console.log(obj2);         // {a:1, b:2, c:30}
const obj3 = {b: 20, c: 30};
const obj4 = {...obj2, ...obj3};  // 合并对象

console.log(obj4);         // {a:1, b:20, c:30}

上面的代码可以看出,使用方式和数组基本一致,都是把数组或对象中的每一项展开到另一个数组或对象中去。

3.4 函数调用时传参问题

在函数调用时经常会向函数中传递参数,但是,当我们的参数是数组中的项时,我们需要把数组中的每一项取出来,然后传入函数中,这样显得很麻烦,能不能有个方式直接把数组传入进去呢?首先我们看个求和的例子:

function sum(x, y) {
  return x + y;
}
console.log(sum(1, 2)); // 3

const data = [1,2];
console.log(sum(data[0], data[1]));  // 3

上面的 sum 是一个求和函数,接受两个参数,我们可以在调用时直接传递 2 个参数。但这个时候我们希望求 data 数组中的和,这个时候只能取出 data 中的每一项值传递到函数中去,这样无疑是一个很笨的方法,在 ES5 的时候可以使用 apply() 对函数进行间接的调用解决这个问题。

function sum(x, y, z) {
  return x + y + z;
}
const data = [1,2,3];
console.log(sum.apply(null, data)); // 6

使用 apply() 的方法是解决了这个问题,但是可能会使我们理解代码增加了难度。有了 ES6 的展开语法,这个问题就会轻而易举地解决了。

function sum(x, y, z) {
  return x + y + z;
}
const data = [1,2,3];
console.log(sum(...data)); // 6

上面的方法使用展开语法把 data 数组中的每一项进行展开,成为 sum 函数中的三个参数。

4. 小结

本节通过数组和字面量的拷贝引入了 ES6 的展开语法的优势,又说到了在函数传参时的应用,可以总结以下几点:

  1. 可以把 ... 加数组或字面量当作数组或字面量中的一项,任意放入数组或字面量中不同的位置;
  2. 可以通过展开语法对数组和字面量进行浅拷贝;
  3. 在函数传参数直接把数组中的项进行展开就可以达到传递多个参数的目的。