本文原载于 SegmentFault 社区
专栏:大前端 
作者:皮小蛋



背景

... 运算符,是 ES6 里一个新引入的运算法,也叫 展开/收集 运算符,我们每天都要和它打交道。

这篇文章,我就带你系统的回顾下这个运算符,介绍一些基础和进阶的用法。

基础篇
先看一下官方描述:
Spread syntax allows an iterable, such as an array expression or string, to be expanded in places where 0 or more arguments or elements are expected or an object expression to be expanded in places where 0 or more key-value pairs (for object literals) are expected.
简而言之就是,... 运算符可以展开一个可迭代对象重的所有项。
可迭代的对象一般是指可以被循环的,包括:string, array, set 等等。

下面我们来看几个基础的例子来加深理解。

基础用法

基础用法 1: 展开

 

const a = [2, 3, 4]
 const b = [1, ...a, 5]

 b; // [1, 2, 3, 4, 5]

基础用法 2: 收集




function foo(a, b, ...c) {
    console.log(a, b, c)     
}

foo(1, 2, 3, 4, 5); // 1, 2, [3, 4, 5]
如果没有命名参数的话,... 就会收集所有的参数:



function foo(...args) {
    console.log(args)     
}

foo(1, 2, 3, 4, 5); // [1, 2, 3, 4, 5]

关于这个收集的用法,官方描述:
“A function’s last parameter can be prefixed with ... which will cause all remaining (user supplied) arguments to be placed within a "standard" javascript array. Only the last parameter can be a rest parameter.”
这个运算符一定是在最后一个参数的位置,也很好理解,就是“收集前面剩下的参数”。
Remember that the rest parameter must be the last parameter, or an error will occur.
如果不在最后一位,会报错。

不得不感叹,这个运算符设计的真的是妙,可展开,可收集,收放自如,当真好用。


基础用法 3: 把 类数组 转换为 数组


先回顾下什么是类数组吧.

类数组和数组非常接近,都可以拥有一系列元素,也有length 属性,最大的不同是:

类数组不具备数组的一系列方法。

举个例子:


const nodeList = document.getElementsByClassName("test");
const array = [...nodeList];

console.log(nodeList); //Result: HTMLCollection [ div.test, div.test ]
console.log(array); //Result: Array [ div.test, div.test ]

使用 ... 就可以实现类数组到数组的转换,转换之后,就可以使用数组的各种方法了。

你还记得在这个操作符出来之前是如何转换的吗?

这个问题还是头条的一个前端面试题。

看例子:



// ES5 时代
function bar() {
  var args = Array.prototype.slice.call(arguments);

   // 调用push 加几个元素
  args.push(1, 2, 3);

  // 把args 作为参数传递给foo
  foo.apply(null, args)

}

// ES6 时代

function foo(...args) { // 搜集参数到 args

  args.push(4, 5, 6)

  console.log(...args) // 展开args

}

bar(0); // 0 1 2 3 4 5 6

基础用法 4: 增加元素或属性

1: 为数组新增成员




const pokemon = ['KK', 'Peter'];
const charmander = '郑伊健';

const pokedex = [...pokemon, charmander];

console.log(pokedex); 

//Result: [ 'KK', 'Peter', '郑伊健' ]

2: 为对象新增属性




const basicSquirtle = { name: 'Squirtle', type: 'Water' };
const fullSquirtle = {
  ...basicSquirtle,
  species: 'Tiny Turtle',
  evolution: 'Wartortle'
};

console.log(fullSquirtle); 

//Result: { name: 'Squirtle', type: 'Water', species: 'Tiny Turtle', evolution: 'Wartortle' }
基础用法 5: 合并数组/对象

合并数组:




const pokemon = ['Squirtle', 'Bulbasur', 'Charmander'];
const morePokemon = ['Totodile', 'Chikorita', 'Cyndaquil'];

const pokedex = [...pokemon, ...morePokemon];

console.log(pokedex); 
//Result: [ 'Squirtle', 'Bulbasur', 'Charmander', 'Totodile', 'Chikorita', 'Cyndaquil' ]

// 对象数组也一样:
const pokemon = [
  { name: 'Squirtle', type: 'Water' },
  { name: 'Bulbasur', type: 'Plant' }
];
const morePokemon = [{ name: 'Charmander', type: 'Fire' }];

const pokedex = [...pokemon, ...morePokemon];

console.log(pokedex); 

//Result: [ { name: 'Squirtle', type: 'Water' }, { name: 'Bulbasur', type: 'Plant' }, { name: 'Charmander', type: 'Fire' } ]

合并对象



const baseSquirtle = {
  name: 'Squirtle',
  type: 'Water'
};

const squirtleDetails = {
  species: 'Tiny Turtle Pokemon',
  evolution: 'Wartortle'
};

const squirtle = { ...baseSquirtle, ...squirtleDetails };
console.log(squirtle); 
//Result: { name: 'Squirtle', type: 'Water', species: 'Tiny Turtle Pokemon', evolution: 'Wartortle' }

以上是一些基础费用法

下面介绍一些... 操作符的进阶用法。

进阶篇

1. 复制具有嵌套结构的数据/对象

先看一个例子:



const pokemon = {
  name: 'Squirtle',
  type: 'Water',
  abilities: ['Torrent', 'Rain Dish']
};

const squirtleClone = { ...pokemon };

pokemon.name = 'Charmander';
pokemon.abilities.push('Surf');

console.log(squirtleClone); 

//Result: { name: 'Squirtle', type: 'Water', abilities: [ 'Torrent', 'Rain Dish', 'Surf' ] }
当我们修改原对象的 name 属性时,我们的克隆对象的 name 属性没有受影响,这是符合我们预期的。

但是当修改原对象的 abilities 属性时,我们的克隆对象也被修改了。

原因也很简单,因为复制过来的 abilities 是一个引用类型,原数据改了,用到他的地方也会跟着改。

知道原因,再解决就很简单了,两种方式:

1、复制引用类型的数据




const pokemon = {
  name: 'Squirtle',
  type: 'Water',
  abilities: ['Torrent', 'Rain Dish']
};

const squirtleClone = { ...pokemon, abilities: [...pokemon.abilities] };

pokemon.name = 'Charmander';
pokemon.abilities.push('Surf');

console.log(squirtleClone);

//Result: { name: 'Squirtle', type: 'Water', abilities: [ 'Torrent', 'Rain Dish' ] }

这样就 OK 了

2、深克隆


在这里就不多解释了。

2. 增加条件属性

顾名思义,就是需要根据条件添加的属性。

看个例子:



const pokemon = {
  name: 'Squirtle',
  type: 'Water'
};

const abilities = ['Torrent', 'Rain dish'];
const fullPokemon = abilities ? { ...pokemon, abilities } : pokemon;

console.log(fullPokemon);

3短路




const pokemon = {
  name: 'Squirtle',
  type: 'Water'
};

const abilities = ['Torrent', 'Rain dish'];
const fullPokemon = {
  ...pokemon,
  ...(abilities && { abilities })
};

console.log(fullPokemon);

如果 abilities 为 true,就相当于是



const fullPokemon = {
  ...pokemon,
  ...{ abilities }
}

这也是一个很有用的技巧。

4. 默认结构和添加默认属性

默认结构:

我们知道,当结构一个对象的时候,如果这个对象里没有某个属性,解出来是undefined , 我们可以添加默认值来解决:



const pokemon = {
  id: 1,
  name: 'Squirtle'
};

const { type, name } = pokemon;
console.log(name); //Result: Squirtle
console.log(type); //Result: undefined

//Assigning default value to the type variable
const { type = 'Water', name } = pokemon;
console.log(type); //Result: Water

添加默认属性

有时候从我们会遇到这样的情况,一个对象,大部分属性是相似的,只有小部分是不不同的,这时候我们就可以设置一个基础对象,具备基础属性,其他的对象可以通过扩展这个对象来得到。
看例子:



const pokemon = {
  name: 'Squirtle',
  type: 'Water'
};

//  给abilities默认赋值
const { abilities = [], ...rest } = pokemon;

const fullSquirtle = { ...rest, abilities };

console.log(rest); //Result: { name: 'Squirtle', type: 'Water' }
console.log({ fullSquirtle }); //Result: { name: 'Squirtle', type: 'Water', abilities: [] }

这里就是通过展开 rest , 合并 abilities 得到完全体的数据。
如果有批量的数据需要处理,这种方法也非常方便:



const pokemon = [
  {
    name: 'Charmander',
    type: 'Fire'
  },
  { name: 'Squirtle', type: 'Water', abilities: ['Torrent', 'Rain Dish'] },
  {
    name: 'Bulbasur',
    type: 'Plant'
  }
];

function setDefaultAbilities(object) {
  const { abilities = [], ...rest } = object;
  return { ...rest, abilities };
}

// Applying the setDefaultAbilities function to all the pokemon in the array:
const normalizedPokemon = pokemon.map(pokemon => setDefaultAbilities(pokemon));

console.log(normalizedPokemon);

//Result: [ { name: 'Charmander', type: 'Fire', abilities: [] },   { name: 'Squirtle', type: 'Water', abilities: [ 'Torrent', 'Rain Dish' ] }, { name: 'Bulbasur', type: 'Plant', abilities: [] } ]

这样迭代一遍,所有的对象就都具备 abilities 属性了。

总结
... 运算符非常灵活,收放自如,非常强大,希望我们都能很好的掌握这个工具。

内容就这么多,希望对大家有所帮助,如有纰漏,欢迎指正。

原创系列推荐



4. 
5. 
6. 
7. 

回复“加群”与大佬们一起交流学习~

点这,与大家一起分享本文吧~

【JS】388- 深入了解强大的 ES6 「 ... 」 运算符的更多相关文章

  1. js基础 js自执行函数、调用递归函数、圆括号运算符、函数声明的提升 js 布尔值 ASP.NET MVC中设置跨域

    js基础 目录 javascript基础 ESMAScript数据类型 DOM JS常用方法 回到顶部 javascript基础 常说的js包括三个部分:dom(文档document).bom(浏览器 ...

  2. 巧用 .NET 中的「合并运算符」获得 URL 中的参数

    获取 URL 中的 GET 参数,无论用什么语言开发网站,几乎是必须会用到的代码.但获取 URL 参数经常需要注意一点就是要先判断是否有这个参数存在,如果存在则取出,如果不存在则用另一个值.这个运算称 ...

  3. ES6,扩展运算符的用途

    ES6的扩展运算符可以说是非常使用的,在给多参数函数传参,替代Apply,合并数组,和解构配合进行赋值方面提供了很好的便利性. 扩展运算符就是三个点“...”,就是将实现了Iterator 接口的对象 ...

  4. [转帖]详解shell脚本括号区别--$()、$「 」、$「 」 、$(()) 、「 」 、「[ 」]

    详解shell脚本括号区别--$().$「 」.$「 」 .$(()) .「 」 .「[ 」] 原创 波波说运维 2019-07-31 00:01:00 https://www.toutiao.com ...

  5. js 模板引擎 - 超级强大

    本来没想写这篇文章,但是网上误导大众的文章太多了,所以今天就抽出半小时时间谈一下我对前端模板引擎的感受吧. 前端模板引擎相信大家都再熟悉不过了,市面上非常多的号称最好.最快.最牛逼的,随便就能找到一大 ...

  6. 《React Native 精解与实战》书籍连载「Node.js 简介与 React Native 开发环境配置」

    此文是我的出版书籍<React Native 精解与实战>连载分享,此书由机械工业出版社出版,书中详解了 React Native 框架底层原理.React Native 组件布局.组件与 ...

  7. js模块化AMD、CMD、ES6

    AMD CMD ES6模块化 各个模块化规范对比理解 一.AMD 在上一篇js模块化入门与commonjs解析与应用中详细的解析了关于commonjs模块化规范,commonjs采用的用同步加载方式, ...

  8. js 模块化的一些理解和es6模块化学习

    模块化 1 IIFE 2 commonjs 3 浏览器中js的模块化 4 简单理解模块加载器的原理  5 es6 之前在参加百度前端技术学院做的小题目的时候,自己写模块的时候 都是写成立即调用表达式( ...

  9. JS创建对象、继承原型、ES6中class继承

    面向对象编程:java中对象的两个基本概念:1.类:类是对象的模板,比如说Leader 这个是泛称领导,并不特指谁.2:实例:实例是根据类创建的对象,根据类Leader可以创建出很多实例:liyi,y ...

随机推荐

  1. [quartusⅡ] 使用quartusⅡ的过程中,遇到过的一些“软件上的问题”

    1.USB blaster的驱动在设备管理器上点“更新驱动软件”,更新不了,说什么哈希值不在指定目录下,如下图, 解决方法是,https://blog.csdn.net/rdgfdd/article/ ...

  2. 【Error】Maven Dependency 下载失败问题

    原文 前言 在使用Maven私服Sonatype Nexus的时候,经常会出现依赖包找不到的问题. 此时通过浏览器去私服页面查看,发现依赖包坐标是存在的,对应的文件(比如jar文件). 或者私服上面也 ...

  3. nyoj 266-字符串逆序输出 (isdigit(), geline(cin, my_string))

    266-字符串逆序输出 内存限制:64MB 时间限制:3000ms 特判: No 通过数:15 提交数:18 难度:0 题目描述: 给定一行字符,逆序输出此行(空格.数字不输出) 输入描述: 第一行是 ...

  4. useReducer代替Redux

    创建state.js import React, { createContext,useContext,useReducer } from 'react'; export const countTex ...

  5. EFK教程(3) - ElasticSearch冷热数据分离

    基于ElasticSearch多实例架构,实现资源合理分配.冷热数据分离 作者:"发颠的小狼",欢迎转载与投稿 目录 ▪ 用途 ▪ 架构 ▪ 192.168.1.51 elasti ...

  6. Discovery and auto register

    1.Discovery 2. auto register 2.1 agent 端配置 2.2 server 端配置

  7. Spring Cloud Alibaba(四)实现Dubbo服务消费

    本项目演示如何使用 Spring Cloud Alibaba 完成 Dubbo 的RPC调用. Spring Cloud与Dubbo Spring Cloud是一套完整的微服务架构方案 Dubbo是国 ...

  8. 【NHOI2018】黑格覆盖

    [题目描述] 在一张由 M * N 个小正方形格子组成的矩形纸张上,有 k 个格子被涂成了黑色.给你一张由 m * n 个同样小正方形组成的矩形卡片,请问该卡片最多能一次性覆盖多少个黑格子? [输入数 ...

  9. 今天是python专场UDP socket 链接

    type = SOCK_DGRAM UDP 协议的通信优势 允许一个服务器的同时和多个客户端通信 server import socket sk = socket.socket(type=socket ...

  10. 2场 J -Subarray

    题意: 长度为1e91e9的(1,−1)(1,−1)序列,下标从00到1e9−11e9−1,已知有nn个区间为11,其他为−1−1, 问存在多少个区间的和>1>1(保证∑1≤i≤nr[i] ...