[譯] 最深入而易懂的ES6解構教程

解構是ES6的新特性,用於從JavaScript對象和數組中提取數據,語法上比ES5所提供的更加簡潔、緊湊、清晰。它不只能減小你的代碼量,還能從根本上改變你的編碼方式。用的越多,你就會發現越多塑造數據和函數的方式,這些實現方式在過去幾乎是不可能的。本文將深刻探討解構賦值,爲你介紹該新特性中你所須要知悉的一切。html

什麼是解構?

解構與構造數據截然相反。 例如,它不是構造一個新的對象或數組,而是逐個拆分現有的對象或數組,來提取你所須要的數據。es6

ES6使用了一種新模式來匹配你想要提取的數值, 解構賦值就是採用了這種模式。 該模式會映射出你正在解構的數據結構,只有那些與該模式相匹配的數據,纔會被提取出來。數組

被解構的數據項位於賦值運算符 = 的右側,能夠是任何數組和對象的組合,容許隨意嵌套。用於給這些數據賦值的變量個數不限。數據結構

本文深刻講解 解構賦值 中你所應知悉的知識點。若是想更好地理解它的工做原理,請參考 數組解構對象解構函數

數組解構

數組解構 使用一個數組做爲一個數據項,你能夠根據 數組模式 (用於從數組中匹配你所須要的數值)從這個數組裏面提取數值給一個或者多個變量賦值。post

數組模式 是根據數值的位置來鑑別哪些值是你想要提取的。它必須能精確地映射數組的結構,來要讓數組模式中的每一個變量都被賦上 被解構數組中 位置與之相對應的值。ui

舉幾個例子來幫助咱們理解吧:this

數組模式示例

把數組中全部的數值賦給一個個單獨的變量
// 設置數組
   const avengers = ['Tony Stark', 'Steve Rogers', 'Natasha Romanoff'];

   // 把數組解構賦值給變量。數組模式位於賦值運算符 `=` 的左側,被結構的數組在
   // 其右側。
   const [ironMan, cap, blackWidow] = avengers;

   // ironMan = 'Tony Stark' 
   // cap = 'Steve Rogers'
   // blackWidow = 'Natasha Romanoff'

   // 輸出 ironMan:
   ironMan;
提取除第一個外的全部數值
const avengers = ['Tony Stark', 'Steve Rogers', 'Natasha Romanoff'];

   // 咱們不用用到Tony
   const [, cap, blackWidow] = avengers;

   // ironMan = Error: undefined 
   // cap = 'Steve Rogers'
   // blackWidow = 'Natasha Romanoff'

   // 輸出 cap:
   cap;
提取除第二個外的全部數值
const avengers = ['Tony Stark', 'Steve Rogers', 'Natasha Romanoff'];

   // cap 缺失
   const [ironMan, , blackWidow] = avengers;

   // ironMan = 'Tony Stark' 
   // cap = Error: undefined
   // blackWidow = 'Natasha Romanoff'

   // 輸出 blackWidow:
   blackWidow;
提取除最後一個外的全部數值
const avengers = ['Tony Stark', 'Steve Rogers', 'Natasha Romanoff'];

   // ironMan vs cap
   const [ironMan, cap] = avengers;

   // ironMan = 'Tony Stark' 
   // cap = 'Steve Rogers'
   // blackWidow = Error: undefined

   // 輸出 blackWidow:
   ironMan;

嵌套數組

這種匹配模式也支持嵌套數組,只要保證賦值運算符 = 左側的數組模式與右側的數組結構相匹配便可。再次說明一下,= 左邊的變量都會被賦上 = 右側數組中位置與之相對應的值。 不管你怎麼深層次地嵌套,仍能夠對它們進行解構。編碼

解構嵌套的數組
// Destructuring Nested Arrays
   const avengers = [
                       'Natasha Romanoff', 
                       ['Tony Stark', 'James Rhodes'], 
                       ['Steve Rogers', 'Sam Wilson']
                    ];

   // Avengers and their partners
   const [blackWidow, [ironMan, warMachine], [cap, falcon]] = avengers;

   // blackWidow = 'Natasha Romanoff'
   // ironMan = 'Tony Stark'
   // warMachine = 'James Rhodes'
   // cap = 'Steve Rogers'
   // falcon = 'Sam Wilson'

   // Output warMachine:
   warMachine;
從深層嵌套的數組中提取一個值
// 從該數組中提取 Pepper Potts
   const avengers = [
                        'Natasha Romanoff', 
                        [['Tony Stark', 'Pepper Potts'], 'James Rhodes'], 
                        ['Steve Rogers', 'Sam Wilson']
                    ];

   // Destructure
   const [ , // 跳過 'Natasha Romanoff'
             [[ , // 跳過 'Tony Stark'
             hera // Pepper Potts 賦值給變量 'hera'
         ]]] = avengers;

   // 請注意:你也能夠這樣寫
   // const [, [[, hera ]]] = avengers;

   // 輸出 hera:
   hera;

   // hera = 'Pepper Potts'

運用rest操做符捕獲全部剩餘項

若是你想要獲取特定的數組項,而且把剩餘的項歸在一個數組,那麼你能夠這樣運用 rest操做符 來解構:.net

// 經過rest操做符解構
   const avengers = ['Natasha Romanoff', 'Tony Stark', 'Steve Rogers'];

   const [blackWidow, ...theOthers] = avengers;

   theOthers;
   // blackWidow = 'Natasha Romanoff'
   // theOthers = ['Tony Stark', 'Steve Rogers']

   // 輸出 theOthers:
   theOthers;

對象解構

對象解構就更神奇了,尤爲是當你須要從一個複雜的、深層嵌套的對象中取值時,其做用更加明顯。重申一下,對象解構與數組解構用的是一樣的規則(即在賦值運算符左側建立一個 對象模式, 使它的變量位置與 = 右側對象的值位置相匹配)。

在對象解構中,你須要指明那些須要被提取值的屬性名稱,以及將要被賦值的變量名。跟數組解構同樣,咱們須要在賦值運算符左邊先建立一個對象模式來映射被解構的對象。

儘管在這種狀況下,咱們想要提取的是 對象屬性的值 (如:咱們從 { prop: value } 中提取 value)。相應地,咱們的對象模式必須有一個變量,這個變量的位置要跟咱們即將提取的屬性值所在的位置一致。

簡單示例

提取一個簡單的對象屬性值

咱們能夠這樣作,來將對象 { ironMan: 'Tony Stark' } 的屬性 ironMan 的值 'Tony Stark' 賦值給變量 a

//解構對象的屬性值,賦給單個變量 `a`:
  const { ironMan: a } = { ironMan: 'Tony Stark' };

  // 輸出 a:
  a;   // a = 'Tony Stark '
提取多個屬性值

咱們只要拓展相同的模式,就能夠從一個對象中提取多個屬性值,以下:

// Setup our object
  const avengers = {
    ironMan: 'Tony Stark', 
    cap: 'Steve Rogers', 
    blackWidow: 'Natasha Romanoff'
  };

  // Destructure object to individual variables
  const { 
    ironMan: a, 
    cap: b, 
    blackWidow: c 
  } = avengers;

  // a = 'Tony Stark '
  // b = 'Steve Rogers'
  // c ='Natasha Romanoff'

  // Output a:
  a;

觀察一下這個解構模式是怎麼確切地匹配 被解構對象 的。

嵌套的對象解構

像解構嵌套數組同樣,咱們能夠對嵌套對象進行解構,無論它的層級多深。

// Setup our object
  const avengers = {
    blackWidow: 'Natasha Romanoff',
    ironManCharacters: {
      couple: {
        ironMan: 'Tony Stark', 
        hera: 'Pepper Potts',
        },
        partner: {
              warMachine: 'James Brodie'
        }
    },
    capCharacters: {
      cap: 'Steve Rogers', 
      partner: {
        falcon: 'Sam Wilson'
      }
    }
  };

  // Destructure object to individual variables
  const { 
    blackWidow: a,
    ironManCharacters: { 
      couple: {
        ironMan: b,
        hera: c
    },
      partner: {
        warMachine: d
      }
    },
    capCharacters: {
      cap: e,
      partner: {
       falcon: f
      }
    }
  } = avengers;

  // a = 'Natasha Romanoff'
  // b = 'Tony Stark '
  // c = 'Pepper Potts'
  // d = 'James Brodie'
  // e = 'Steve Rogers'
  // f = 'Sam Wilson'

  // Output a:
  a;

給賦值的變量命名

固然,把變量名設爲諸如 a, b, c 之類,是很糟糕的,變量名稱應該是有意義的。

冗長式命名
// Setup our object
  const avengers = {
    ironMan: 'Tony Stark', 
    cap: 'Steve Rogers', 
    blackWidow: 'Natasha Romanoff'
  };

  // Destructure object to individual variables with meaningful names
  const { 
    ironMan: ironMan,
    cap: cap, 
    blackWidow: blackWidow
  } = avengers;

  // blackWidow = 'Natasha Romanoff'
  // ironMan = 'Tony Stark '
  // cap = 'Steve Rogers'

  // Output blackWidow:
  blackWidow;

這種作法比上面用 a,b,c 命名好,可是仍然能夠完善。 { ironMan: ironMan } 看起來有點醜並且不直觀。

語法上命名捷徑

若是你要把一個對象的屬性值賦給一個變量,該變量的名稱跟對象的屬性名稱同樣,那麼在 = 左側的賦值模式裏面,你只須要簡單地寫屬性名便可,以下:

// Setup our object
  const avenger = {
    ironMan: 'Tony Stark'
  };

  // Destructure object to individual variables with meaningful names
  const { 
    ironMan   // equivalent to 'ironMan: ironMan'
  } = avenger;

  // ironMan = 'Tony Stark '

  // Output ironMan:
  ironMan;

因爲 被解構的對象屬性名稱被賦值的變量名稱 相同,咱們只須要把名稱列出來一次便可。

語法簡潔

咱們稍微從新修整下前面的代碼,就可使它們看起來更加簡潔明瞭:

// Setup our object
  const avengers = {
    ironMan: 'Tony Stark', 
    cap: 'Steve Rogers', 
    blackWidow: 'Natasha Romanoff'
  };

  // Destructure object to individual variables with meaningful names
  const { ironMan, cap, blackWidow } = avengers;

  // Output ironMan:
  ironMan;

從對象中提取一個深層嵌套的屬性

當咱們要提取一個深層嵌套的對象屬性時,事情就更有趣了:

// Setup our object
const avengers = {
   blackWidow: 'Natasha Romanoff',
   ironManCharacters: {
      couple: {
         ironMan: 'Tony Stark',
         hera: 'Pepper Potts',
      },
      partner: {
         warMachine: 'James Brodie'
      }
   },
   capCharacters: {
      cap: 'Steve Rogers',
      partner: {
         falcon: 'Sam Wilson'
      }
   }
};

// Destructure a deeply nested object
const { ironManCharacters: { couple } } = avengers;

// couple = {
//    ironMan: 'Tony Stark', 
//    hera: 'Pepper Potts',
// }

// Output couple:
couple;

等等,你是怎麼閱讀這段代碼的?couple 這個變量又是怎麼被定義的呢?

經過這樣拆分,咱們就能夠看出賦值運算符 = 左側是被解構對象的一個映射:

const avengers = {
    ironManCharacters: {
      couple: {
          ironMan: 'Tony Stark', 
          hera: 'Pepper Potts',
      }
    }
};

const { 
   ironManCharacters: { 
      couple 
   }
} = avengers;

// Output couple:
couple;

僅僅使用 const { couple } = avengers; 並無辦法提取出 couple 的值。只有把要提取的對象屬性位置名稱映射出來,JS 編譯器才能獲得相應的信息,沿着對象的全部屬性往下查找,並準確地提取咱們想要的值。

這裏也要注意到 couple 用了語法捷徑給變量命名,其實是這樣的:

const { 
   ironManCharacters: { 
      couple: couple
   }
} = avengers;

couple 就是這樣被定義的,它的值就是對象 avengers 中屬性名爲 couple 的值。

給對象的屬性解構賦值

到目前爲止,咱們都是解構對象的值來給單個的變量賦值,其實還能夠給另外一個對象的屬性賦值。

const avengers = {
  blackWidow: 'Natasha Romanoff',
  ironManCharacters: {
    couple: {
      ironMan: 'Tony Stark',
      hera: 'Pepper Potts'
    }
  }
};

const ironManProperties = {
  family: {}
};

({
  ironManCharacters: {
    couple: ironManProperties.family
  }
} = avengers);

ironManProperties.family
// ironManProperties.family = {
//    ironMan: 'Tony Stark',
//    hera: 'Pepper Potts'
// }

// Output ironManProperties.family:
ironManProperties.family;

在這裏咱們把 ironManCharacters.couple 的值賦給了 ironManProperties.family 這個屬性,這裏有兩點須要說明一下:

1. 解構賦值必須被包含在 圓括號

當咱們在對一個已存在的變量(如上面例子中的 ironManProperties)進行解構時,必定要這樣作,而不是去聲明一個新的變量。

2. 模式仍然相匹配

{ ironManCharacters: { couple... } } 與對象 avengers 中的 ironManCharacters 相匹配。這樣就能如你所願,從 avengers 對象中提取出 ironManCharacters.couple 的值了。可是如今,couple 後面放置了一個新的對象ironManProperties 和它的屬性 family,其實被賦值的就是這個對象的屬性ironManProperties.family了。

當你嘗試把這種狀況解釋清楚時,是否還有所困惑呢?在jsfiddle裏面嘗試上面的代碼,一切就明瞭了。

若是你不清楚本身爲何要這樣作,請參考下一篇文章的例子。這些例子會告訴你,爲何採用這種模式來解構API調用的 JSON 對象,讓你領略解構的神奇之處!

默認值

解構時,你還能夠給變量指定一個默認值:

// Setup our object
  const avengers = {
    ironMan: 'Tony Stark', 
    cap: 'Steve Rogers', 
    blackWidow: 'Natasha Romanoff'
  };

  // Destructure using defaults
  const { ironMan, cap, blackWidow, theHulk='Bruce Banner' } = avengers;

  // ironMan = 'Tony Stark' 
  // cap = 'Steve Rogers'
  // blackWidow = 'Natasha Romanoff'
  // theHulk = 'Bruce Banner'

  // Output blackWidow:
  blackWidow;

解構時要避免出現這些問題

解構賦值時沒有使用 const, let, var

在講到對 對象屬性 進行解構賦值時就已經說起了這一點,但這裏仍是有必要再重申一下,讓你們有個深入的印象。

不能對已經聲明的變量進行解構

也就是說,你只能在對變量解構賦值的同時聲明變量。

// Setup our object
   const avengers = {
     ironMan: 'Tony Stark', 
     cap: 'Steve Rogers', 
     blackWidow: 'Natasha Romanoff',
     theHulk: 'Bruce Banner'
   };

   // Valid destructuring
   const { ironMan } = avengers;

   let { cap } = avengers;

   var { blackWidow } = avengers;

   // Invalid destructuring
   let theHulk;

   { theHulk } = avengers;
   // Error

   // Output theHulk:
   theHulk;

爲什麼不能對一個已經聲明的變量進行解構呢?那是由於這時若是你使用了花括號 { ,JavaScript會認爲你是在聲明一個 block

解決的辦法就是把整個解構賦值用一對 圓括號 括起來。

如何對一個已聲明的變量進行解構賦值
// Setup our object
   const avengers = {
     ironMan: 'Tony Stark', 
     cap: 'Steve Rogers', 
     blackWidow: 'Natasha Romanoff',
     theHulk: 'Bruce Banner'
   };

   // A valid Hulk
   let theHulk;

   ({ theHulk } = avengers);
   // theHulk = 'Bruce Banner'

   // Output theHulk:
   theHulk;

如今咱們不是以花括號開頭,因此JS不會認爲咱們是在聲明一個 block ,這樣就能夠達到預期的解構結果。

直接返回一個被解構的值

在沒有先聲明一個接下來要被返回的變量時,就直接返回一個被解構的值,這樣是沒法達到預期效果的。例如,下面的代碼中,返回的將是整個 ironMan對象,而不是預期要的值 Tony Stark

// Note: this doesn't work!
  function getTonyStark(avengers){
    return { ironMan: { realName } } = avengers;
    // return the avengers object, not the realName value
  }

  const avengers = {
    ironMan: {
      realName: 'Tony Stark'
    }
  };

  const tonyStark = getTonyStark(avengers);

  // tonyStark = {
  //   ironMan: {
  //     realName: 'Tony Stark'
  //   }
  // };

  // Output tonyStark:
  tonyStark;

要從一個被解構的對象中提取值,必須先把它賦值給一個變量,而後再把這個變量返回,以下代碼所示:

// Note: this DOES work!
  function getTonyStark(avengers){
    const { ironMan: { realName } } = avengers;
    return realName;
  }

  const avengers = {
    ironMan: {
      realName: 'Tony Stark'
    }
  };

  const tonyStark = getTonyStark(avengers);

  // tonyStark = 'Tony Stark'

  // Output tonyStark:
  tonyStark;

這種把賦值和返回分紅兩行代碼的作法實在惹人厭煩,代碼醜陋,也顯得不必。但很不幸,JavaScript就是這樣工做的----你必須先把解構的值賦給一個變量,而後再把它返回,兩步必須分開作。

可是,沒有說咱們只是說分開作,並無說必定要擺成兩行代碼,因此像下面這樣寫成一行,也是能達到預期效果的:

function getTonyStark(avengers){
    return ({ ironMan: { realName } } = avengers) && realName;
  }

  const avengers = {
    ironMan: {
      realName: 'Tony Stark'
    }
  };

  const tonyStark = getTonyStark(avengers);
  // tonyStark = 'Tony Stark'

  // Output tonyStark:
  tonyStark;

因爲JavaScript的 _short-circuit_ 邏輯操做符 (&& and ||) 會基於第一個操做數的值來返回第二個操做數的值,因此這種寫法可以達到預期效果。這裏,第一個操做數是解構賦值表達式,把值賦給 realName。而 realName 也就是第二個操做數,因此它的值最終被返回。

這樣作不是最佳的,可是能實現。在追求代碼簡短的同時,必定要注意代碼的可讀性。

總結

本文深刻講解了 解構賦值 的主要原則。雖然這樣讓你明白瞭解構是若是工做的,可是還不足以向你闡明如何真正運用這個強大的概念。

所以,下一篇文章,我會羅列一些高級的解構技巧,真正地展現解構的魔力,這些方式你可能從未思考過。

拓展閱讀

ES6解構篇2 – 高級技巧
本文的jsfiddle
網上關於解構的文章

若是你還想閱讀更多,請看下面連接:

相關文章
相關標籤/搜索