­

[第12期]深入了解強大的 ES6 「 … 」 運算符

引言

我沒有上班摸魚,假期還沒結束,明天才上班。

在家寫了一篇比較基礎的文章,花了好幾個小時, 在這分享給大家。

背景

... 運算符, 是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 屬性, 最大的不同是:

類數組不具備數組的一系列方法。

舉個例子:


使用 ... 就可以實現類數組到數組的轉換, 轉換之後, 就可以使用數組的各種方法了。

你還記得在這個操作符出來之前是如何轉換的嗎?

這個問題還是頭條的一個前端面試題

看例子:

// 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, 就相當於是


這也是一個很有用的技巧。

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進階 」,一起學習成長

關注我啦

可以通過公眾號菜單欄的聯繫我, 了解我們的微信群, 謝謝。

我就知道你「在看」