• 【JS】388- 深入了解强大的 ES6 「 ... 」 运算符


    本文原载于 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(12345); // [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 属性,最大的不同是:

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

    举个例子:


    640?wx_fmt=png

    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(123);

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

    }

    // ES6 时代

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

      args.push(456)

      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. 

    640?wx_fmt=png

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

    640?wx_fmt=png
    点这,与大家一起分享本文吧~
    个人博客:http://www.pingan8787.com 微信公众号【前端自习课】和千万网友一起,每日清晨,享受一篇前端优秀文章。 目前已连续推送文章 600+ 天,愿每个人的初心都能一直坚持下去!
  • 相关阅读:
    Linux 文件和目录的属性
    关于样式加载顺序,js加载顺序
    关于css切换菜单
    jquery插件编写思路
    京东中关于领券地址的安全处理
    把js写到链接a标签的href中和写到onclick中的区别
    京东中关于jsonp的运用
    关于Jquery中ajax方法data参数用法的总结
    js中的延迟加载
    Jquery版Ajax利用JSONP 跨域POST/GET传输数据研究
  • 原文地址:https://www.cnblogs.com/pingan8787/p/11838051.html
Copyright © 2020-2023  润新知