扩展运算符(Spread Operator)和剩余参数(Rest Parameter)的写法相同,都是在变量或字面量之前加三个点(...),并且只能用于包含Symbol.iterator属性的可迭代对象(iterable)。虽然两者之间有诸多类似,但它们的功能和应用场景却完全不同。扩展运算符能把整体展开成个体,常用于函数调用、数组或字符串处理等;而剩余参数正好相反,把个体合并成整体,常用于函数声明、解构参数等。此处的整体可能是数组、字符串或类数组对象等,个体可能是字符、数组的元素或函数的参数等。
## 一、扩展运算符
  扩展运算符的用途简单概括,可以分为以下三种。
  (1)替代函数的apply()方法。
  (2)简化函数调用时传递实参的方式。
  (3)处理数组和字符串。
**1)apply()**
  函数的apply()方法能够间接调用其它对象的方法,往往能收获奇效,例如用Math对象的min()方法获取数组中的最小值,min()方法本来只接收一组参数,利用apply()方法后就能直接传递一个数组,如下所示。
~~~
let arr = [1, 0, 2],
min;
min = Math.min(1, 0, 2); //一组参数的调用方式
min = Math.min.apply(undefined, arr); //利用apply()间接调用
~~~
  虽然apply()方法很便捷,但每次都必须设置this的指向(即定义第一个参数),并且迂回的写法可能会为理解代码意图设置障碍。而使用扩展运算符后,既能以简单的语法形式完成相同的功能,还能更清晰的表明代码的意图。下面用扩展运算符查找数组中的最小值。
~~~
min = Math.min(...arr);
console.log(min); //0
~~~
**2)传参**
  函数在被调用时,实参通常都是以逗号分隔的序列形式传递到函数体内。如果实参的值被保存在数组中,那么就要一个一个的读取数组中指定位置的元素,例如创建一个日期对象(调用它的构造函数),把年月日的信息保存在数组中,如下代码所示。注释中的日期并不是默认的显示格式,只是为了更容易阅读而这么写的。
~~~
let date = [2018, 6, 9];
new Date(date[0], date[1], date[2]); //2018-7-6
~~~
  换成扩展运算符的写法后,实参的传递就变得非常的简洁,如下所示。
~~~
new Date(...date); //2018-7-6
~~~
  不仅如此,在调用函数的时候,还可以使用多个扩展运算符,并能和普通的实参混合使用,如下所示。
~~~
let time = [10, 28];
new Date(...date, ...time, 45); //2018-7-6 10:28:45
~~~
**3)数组和字符串**
  在扩展运算符出现之前,要执行数组的复制、合并等操作,需要调用数组的slice()、concat()、unshift()等方法。这些方法到底是单独调用还是组合调用,由实际情况而定。下面是一个数组复制与合并的简单示例。
~~~
let arr1 = [1, 2, 3],
arr2,
arr3;
arr2 = arr1.slice(); //复制数组
arr3 = arr2.concat(arr1); //合并数组
console.log(arr1); //[1, 2, 3]
console.log(arr2); //[1, 2, 3]
console.log(arr3); //[1, 2, 3, 1, 2, 3]
~~~
  接下来用扩展运算符来完成同样的功能,如下代码所示。
~~~
arr2 = [...arr1]; //复制数组
arr3 = [...arr1, ...arr2]; //合并数组
~~~
  在实际项目中,肯定会碰到各式各样的数组操作,合理利用扩展运算符,不但可以节省大量的代码,还能提升代码的可读性。
  扩展运算符不仅能处理数组,还能处理字符串。在JavaScript中,字符串的行为类似于数组,但它不能直接调用数组的方法,需要先执行自己的split()方法转换成数组。而使用扩展运算符后,就能省去这步操作,具体如下所示,注意,包裹的方括号不能省略。
~~~
let str = "strick";
str.split(""); //["s", "t", "r", "i", "c", "k"]
[...str]; //["s", "t", "r", "i", "c", "k"]
~~~
## 二、剩余参数
  在JavaScript的函数中,声明时定义的形参个数可以和传入的实参个数不同。当实参个数大于形参个数时,ES6新增的剩余参数能把没有对应形参的实参收集到一个数组中。下面是一个简单的示例。
~~~
function func(name, ...args) {
console.log(name);
console.log(args[0]);
}
func("strick"); //首先输出"strick",然后输出undefined
func("freedom", 29); //首先输出"freedom",然后输出29
~~~
  第一次调用func()函数只传入了一个实参,对应的形参就是name。第二次调用func()函数传入了两个实参,第一个有对应的形参,而第二个并没有对应的形参。此时,该实参就会被放到数组args(就是剩余参数)中,变为该数组的一个元素,在函数体内就能通过数组的索引读取该实参。有一点要注意,剩余参数不会影响函数的length属性,该属性的值表示形参个数。以上面的func()函数为例,... args并不是一个形参,因此,func()函数的length属性值为1。
~~~
console.log(func.length); //1
~~~
**1)解构**
  剩余参数可以被解构(将在第3篇中讲解),这意味着剩余参数中的元素可以被赋给函数体中的同名变量,如下所示。
~~~
function destructuring (name, ...[age]) {
console.log(name);
console.log(age);
}
destructuring ("jane", 28); //首先输出"jane",然后输出28
~~~
  引入剩余参数就是为了能替代函数内部的arguments,它是一个类数组对象,管理着实参列表,该列表包含了传入到函数内的所有实参。由于arguments对象不具备数组的方法,所以很多时候在使用之前要先转换成一个数组。而剩余参数本来就是一个数组,避免了这多余的一步,使用起来既优雅又自然。
**2)两点限制**
  剩余参数有两点限制,在使用时需要引起注意。第一点是在函数中声明时必须放在最后,下面是一种错误的写法。
~~~
function restrict1(...args, name) {
//抛出语法错误
}
~~~
  第二点是不能在对象字面量的setter方法中声明,因为该方法只接收一个参数,而剩余参数不会限制参数的数量。注意,setter方法在定义时会用set替代function关键字,下面是一个会抛出语法错误的例子。
~~~
var obj = {
set age(...value) {
this._age = value;
}
};
~~~
*****
> 原文出处:
[博客园-ES6躬行记](https://www.cnblogs.com/strick/category/1372951.html)
[知乎专栏-ES6躬行记](https://zhuanlan.zhihu.com/pwes6)
已建立一个微信前端交流群,如要进群,请先加微信号freedom20180706或扫描下面的二维码,请求中需注明“看云加群”,在通过请求后就会把你拉进来。还搜集整理了一套[面试资料](https://github.com/pwstrick/daily),欢迎浏览。
![](https://box.kancloud.cn/2e1f8ecf9512ecdd2fcaae8250e7d48a_430x430.jpg =200x200)
推荐一款前端监控脚本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不仅能监控前端的错误、通信、打印等行为,还能计算各类性能参数,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、扩展运算符和剩余参数
- 3、解构
- 4、模板字面量
- 5、对象字面量的扩展
- 6、Symbol
- 7、代码模块化
- 8、数字
- 9、字符串
- 10、正则表达式
- 11、对象
- 12、数组
- 13、类型化数组
- 14、函数
- 15、箭头函数和尾调用优化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、类
- 21、类的继承
- 22、Promise
- 23、Promise的静态方法和应用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基础实践
- 3、WebRTC视频通话
- 4、Web音视频基础
- CSS进阶
- 1、CSS基础拾遗
- 2、伪类和伪元素
- 3、CSS属性拾遗
- 4、浮动形状
- 5、渐变
- 6、滤镜
- 7、合成
- 8、裁剪和遮罩
- 9、网格布局
- 10、CSS方法论
- 11、管理后台响应式改造
- React
- 1、函数式编程
- 2、JSX
- 3、组件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表单
- 8、样式
- 9、组件通信
- 10、高阶组件
- 11、Redux基础
- 12、Redux中间件
- 13、React Router
- 14、测试框架
- 15、React Hooks
- 16、React源码分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基础
- 4、webpack进阶
- 5、Git
- 6、Fiddler
- 7、自制脚手架
- 8、VSCode插件研发
- 9、WebView中的页面调试方法
- Vue.js
- 1、数据绑定
- 2、指令
- 3、样式和表单
- 4、组件
- 5、组件通信
- 6、内容分发
- 7、渲染函数和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、数据类型
- 2、接口
- 3、类
- 4、泛型
- 5、类型兼容性
- 6、高级类型
- 7、命名空间
- 8、装饰器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系统和网络
- 3、命令行工具
- 4、自建前端监控系统
- 5、定时任务的调试
- 6、自制短链系统
- 7、定时任务的进化史
- 8、通用接口
- 9、微前端实践
- 10、接口日志查询
- 11、E2E测试
- 12、BFF
- 13、MySQL归档
- 14、压力测试
- 15、活动规则引擎
- 16、活动配置化
- 17、UmiJS版本升级
- 18、半吊子的可视化搭建系统
- 19、KOA源码分析(上)
- 20、KOA源码分析(下)
- 21、花10分钟入门Node.js
- 22、Node环境升级日志
- 23、Worker threads
- 24、低代码
- 25、Web自动化测试
- 26、接口拦截和页面回放实验
- 27、接口管理
- 28、Cypress自动化测试实践
- 29、基于Electron的开播助手
- Node.js精进
- 1、模块化
- 2、异步编程
- 3、流
- 4、事件触发器
- 5、HTTP
- 6、文件
- 7、日志
- 8、错误处理
- 9、性能监控(上)
- 10、性能监控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 监控系统
- 1、SDK
- 2、存储和分析
- 3、性能监控
- 4、内存泄漏
- 5、小程序
- 6、较长的白屏时间
- 7、页面奔溃
- 8、shin-monitor源码分析
- 前端性能精进
- 1、优化方法论之测量
- 2、优化方法论之分析
- 3、浏览器之图像
- 4、浏览器之呈现
- 5、浏览器之JavaScript
- 6、网络
- 7、构建
- 前端体验优化
- 1、概述
- 2、基建
- 3、后端
- 4、数据
- 5、后台
- Web优化
- 1、CSS优化
- 2、JavaScript优化
- 3、图像和网络
- 4、用户体验和工具
- 5、网站优化
- 6、优化闭环实践
- 数据结构与算法
- 1、链表
- 2、栈、队列、散列表和位运算
- 3、二叉树
- 4、二分查找
- 5、回溯算法
- 6、贪心算法
- 7、分治算法
- 8、动态规划
- 程序员之路
- 大学
- 2011年
- 2012年
- 2013年
- 2014年
- 项目反思
- 前端基础学习分享
- 2015年
- 再一次项目反思
- 然并卵
- PC网站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端学习之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 日志
- 2020