深入了解强大的 ES6 「 ... 」 运算符

引言

我没有上班摸鱼,假期还没结束,明天才上班。 在家写了一篇比较基础的文章,花了好几个小时, 在这分享给大家。

背景

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

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

基础篇

先看一下官方描述:

Spread syntax allows an iterable, such as an array expressionor string, to be expanded in places where 0 or more argumentsor elementsare expected or an object expressionto 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 parametercan 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 errorwill 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);

也可以简化一下:

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

3: 短路

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

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

console.log(fullPokemon);

如果 abilitiestrue, 就相当于是

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

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

3: 默认结构和添加默认属性

默认解构:

我们知道, 当结构一个对象的时候, 如果这个对象里没有某个属性, 解出来是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属性了。

总结

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

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

最后

觉得内容有帮助可以关注下我的公众号 「 前端e进阶 」,一起学习成长

可以通过公众号菜单栏的联系我, 了解我们的微信群, 谢谢。

原文链接:segmentfault.com

上一篇:jsbundler
下一篇:jsdelta

相关推荐

  • (深入)使用jQuery复制数组(复制)

    Communitymorgancodes(https://stackoverflow.com/users/1/community)提出了一个问题:(Deep) copying an array usi...

    2 年前
  • 领略原生 javascript es6~es10 的魅力

    图片描述(https://img.javascriptcn.com/f3663fe101c5d4062fda74e1ae5e25d6 "图片描述") 作为前端开发工程师,盲目追逐框架似乎有点舍本逐末...

    8 个月前
  • 领略原生 JavaScript ES6~ES10 的魅力

    图片描述(https://img.javascriptcn.com/98bc3ace8fade84c91d94f42b7cc5301 "图片描述") 作为前端开发工程师,盲目追逐框架似乎有点舍本逐末...

    8 个月前
  • 面试题:没有es6老项目,如何用jq解决异步的问题?

    我们都知道es6提供了promise异步写法,但是大部分的公司都是jq写的,那我们如何用Jq来写和promise异步一样的写法呢?这个知道的人不多下面我们就来写写把 注意: 1 JQ 1.5以上 ...

    2 年前
  • 面试题3:ES5、ES6两种方式实现继承

    写一个类Person,拥有属性age和name,拥有方法say(something)再写一个类Superman,继承Person,拥有自己的属性power,拥有自己的方法fly(height) ...

    22 天前
  • 面试深入一、ES6模块化、安装和打包

    开发环境已经普及使用 浏览器环境却支持不好(需要开发环境编译) 内容很多,重点了解常用语法 面试:开发环境的使用 重点语法的掌握 问题 模块化如何使用...

    2 年前
  • 零代码深入浅出React并发模式,带你理解React Fiber架构

    React Fiber架构有一定的复杂度,如果硬着头皮去啃源码,我们会深陷于庞大的代码量和实现细节之中,往往学不到什么东西。 React并发模式是ReactFiber架构的重要应用,本文不贴任何Rea...

    4 个月前
  • 零代码深入浅出React并发模式

    本文不贴任何React源码,纯粹使用文字讲述React并发模式的原理。 这不是一篇关于React并发模式API使用的文章。 为节约篇幅,下文不会详细介绍API的用法,只会讲解原理。

    4 个月前
  • 阮一峰ES6全面回顾

    9月抽空重新回顾了下ES6所有知识点,整个回顾过程既惊喜又感慨,感慨开发这么久好像真的没有好好的静下心去读一本好的书,大多情况下只是在使用的时候用到了,不熟悉或者感兴趣再去走马观花一通,感慨之余也发...

    7 个月前
  • 逻辑运算符的短路求值

    逻辑运算符 短路求值 (参考:https://developer.mozilla.org...(https://developer.mozilla.org/zhCN/docs/Web/...

    1 年前

官方社区

扫码加入 JavaScript 社区