ES6+ 剩余参数

1. 前言

上节我们学习了展开语法,本节我们学习与之相反的操作 —— 剩余语法(Rest syntax 也可以叫剩余参数)看起来和展开语法完全相同都是使用 ... 的语法糖,不同之处在于剩余参数用于解构数组和对象。从某种意义上说,剩余语法与展开语法是相反的:展开语法将数组展开为其中的各个元素,而剩余语法则是将多个元素收集起来成为一个整体。

2. 函数参数

在讲解剩余参数前,我们先来看看,剩余参数在函数参数中都解决了哪些问题?为什么会引入剩余参数的概念?

在 ES5 中,函数经常会传入不定参数,在传入不定参数时,ES5 的给出的解决方案是通过 arguments 对象来获取函数调用时传递的参数。 arguments 对象不是一个数组,它是一个类数组对象,所谓类数组对象,就是指可以通过索引属性访问元素并且拥有 length 属性的对象。

一个简单的类数组对象是长这样的:

var arrLike = {
  0: 'name',
  1: 'age',
  2: 'job',
  length: 3
}

而它所对应的数组应该是这样子的:

var arr = ['name', 'age', 'job'];

这里我们说类数组对象与数组的性质相似,是因为类数组对象在访问赋值获取长度上的操作与数组是一致的,具体内容可查阅相关的类数组使用。

在函数体中定义了 Arguments 对象,其包含函数的参数和其它属性,以 arguments 变量来指代。下面我们看个实例:

function fn() {
    console.log(arguments);
}
fn('imooc', 7, 'ES6')

在控制台中打印出上面的代码结果,如下图所示:在定义函数的时候没有给定参数,但是通过 arguments 对象可以拿到传入的参数。可以看到 arguments 中包含了函数传递的参数、length 等属性,length 属性表示的是实参的长度,即调用函数的时候传入的参数个数。这样我们就对 arguments 对象有了一定的了解。

图片描述

在 ES5 的开发模式下,想要使用传递的参数,则需要按位置把对应的参数取出来。尽管 arguments 是一个类数组且可遍历的变量,但它终究不是数组,它不支持数组方法,因此我们不能调用 arguments.forEeach (…) 等数组的方法。需要使用一些特殊的方法转换成数组使用,如:

function fn() {
  var arr = [].slice.call(arguments);
  console.log(arr)
}
fn('ES6');
//  ["ES6"]
fn('imooc', 7, 'ES6');
//  ["imooc", 7, "ES6"]

终于借助 call 方法把 arguments 转化成一个真正的数组了。但是这样无疑是一个繁琐的过程,而且不容易理解。这时 ES6 给出了它的完美解决方案 —— 剩余参数,那剩余参数是如何在函数传参中使用的呢?下面我们来看看实例:

function fn(...args) {
  console.log(args)
}
fn('ES6');
//  ["ES6"]
fn('imooc', 7, 'ES6');
//  ["imooc", 7, "ES6"]

使用方式很简单在函数定义时使用 ... 紧接着跟一个收集的参数,这个收集的参数就是我们所传入不定参数的集合 —— 也就是数组。这样就很简单地摆脱了 arguments 的束缚。另外,还可以指定一个默认的参数,如下示例:

function fn(name, ...args) {
  console.log(name);  // 基础参数
  console.log(args);  // 剩下的参数组成的数组
}
fn('ES6');
//	'ES6'
//	[]
fn('imooc', 7, 'ES6');
//  "imooc"
//	[7, "ES6"]

上面的代码中给函数第一个参数,声明一个变量 name,剩余的参数会被 ... 收集成一个数组,这就是剩余参数。引入剩余参数就是为了能替代函数内部的 arguments,由于 arguments 对象不具备数组的方法,所以很多时候在使用之前要先转换成一个数组。而剩余参数本来就是一个数组,避免了这多余的一步,使用起来既优雅又自然。

2. 解构剩余参数

ES6 允许按照一定模式,从数组和对象中提取值,并对变量进行赋值,这被称为解构(下节我们会讲到)。 比如如下代码:

let array = [1, 2, 3]
let [a, b, c] = array;
console.log(a);				 // 1
console.log(b);				 // 2
console.log(c);				 // 3

再比如如下代码:

let obj = {a:1, b:2, c:3}
let {a, b, c} = obj;
console.log(a);				// 1
console.log(b);				// 2
console.log(c);			  // 3

上面的两个例子,就是数组和对象的解构赋值过程,在解构赋值时,可以使用剩余操作符。剩余操作符所操作的变量会匹配在解构赋值中所有其他变量未匹配到的属性。看如下示例:

let {a, b, ...others } = {a: 1,  b: 2,  c: 3,  d: 4,  e: 5}
console.log(a);					// 1
console.log(b);					// 2
console.log(others);		// {c: 3,  d: 4,  e: 5}

上面的代码中,a、b 会匹配对象中对应的值,...others 则会收集匹配余下的属性值,并打包起来构造一个新的对象赋值给了 others

数组也可以通过剩余操作符,把剩余的元素打包成一个新的数组赋值给剩余属性,代码如下:

let array = [1, 2, 3, 4, 5];
let [a, b, ...others] = array;
console.log(a);					 // 1
console.log(b);					 // 2
console.log(others);		 // [3,4,5]

在函数传参的时候也可以是和解构一起使用。如下所示。

function fun(...[a, b, c]) {
  return a + b + c;
}
fun('1')          // NaN (b 和 c 都是 undefined)
fun(1, 2, 3)      // 6
fun(1, 2, 3, 4)   // 6 多余的参数不会被获取到

上面的代码中,a、b、c 会去解构传入参数,加上有剩余语法的作用,对应的值从数组中的项解构出来,在函数内部直接使用解构出来的参数即可。剩余语法看起来和展开语法完全相同,不同点在于,剩余参数用于解构数组和对象。

3. 小结

本节结合了 ES5 函数中的 arguments 对象引入了为什么 ES6 会引入剩余参数的概念,可以看到剩余参数所带来的好处。本节内容可以总结以下几点:

  1. 剩余参数是为了能替代函数内部的 arguments 而引入的;
  2. 和展开语法相反,剩余参数是将多个单个元素聚集起来形成一个单独的个体的过程。