解構(destructuring)是一種賦值語法,可從數組中提取元素或從對象中提取屬性,將其值賦給對應的變量或另外一個對象的屬性。解構地目的是簡化提取數據的過程,加強代碼的可讀性。有兩種解構語法,分別是數組解構和對象解構,二者的區別在於解構賦值語句的左側,前者是數組字面量,然後者是對象字面量。爲了說明解構的優點,下面用一個例子來對比手動賦值和解構賦值。html
var arr = [1, 2], obj = { a: 3, b: 4 }, x, y, a, b; x = arr[0]; y = arr[1]; a = obj.a; b = obj.b; var [x, y] = [1, 2]; //數組解構 var { a, b } = { a: 3, b: 4 }; //對象解構
從上面的代碼中可看出,解構賦值只需2條語句,就能完成7條左右的語句才能實現的手動賦值。接下來會先介紹兩種解構語法都包含的通用特性,而後分別講解二者所獨有的特性,再對它們的兩個特性作對比,最後會分析一種應用解構的特殊場景:函數中的參數解構。git
解構賦值的語句能夠包含或忽略聲明關鍵字(例如var、let等),若是包含,那麼就須要初始化,不然會報語法錯誤,下面是兩種不正確的寫法。數組
var [x, y]; var { a, b };
若是忽略聲明關鍵字,那麼在運行對象解構的時候,必須用圓括號包裹賦值表達式,而數組解構則並不強制,以下所示。dom
[x, y] = [1, 2]; //無圓括號的數組解構 ({ a, b } = { a: 3, b: 4 }); //有圓括號的對象解構
之因此要用圓括號包裹,是由於表達式左側的花括號會被解析成代碼塊而不是對象字面量。若是把代碼塊和等號運算符放在一行,那麼就會報語法錯誤。函數
當執行解構語句時,若是等號左側的變量或對象屬性沒有從右側找到對應的數組元素或對象屬性,那麼就會被賦爲undefined,以下所示。優化
[c, d] = [1]; ({ e, f } = { e: 3 }); console.log(d); //undefined console.log(f); //undefined
1)位置spa
在數組解構時,解構會按順序做用於數組的元素上,也就是說,變量或對象屬性要取誰的值與它所在的位置有關。下面是一個交換變量位置的例子。code
[x, y] = [1, 2]; console.log(x, y); //1 2 [y, x] = [1, 2]; console.log(x, y); //2 1
位置交換後,變量被賦的值也會隨之改變。若是將變量替換爲對象屬性,那麼獲得的結果也是相同的,以下所示。htm
var obj = { x, y }; [obj.x, obj.y] = [1, 2]; console.log(obj.x, obj.y); //1 2 [obj.y, obj.x] = [1, 2]; console.log(obj.x, obj.y); //2 1
在經典的冒泡排序中,會交換兩個值的位置,傳統的作法是用一個臨時變量作中轉。而若是用解構的語法,那麼就能夠省略那個臨時變量,而且寫法更爲簡潔。二者的對好比下所示。對象
var x = 1, y = 2; [x, y] = [y, x]; //數組解構 var tmp = x; //傳統作法 x = y; y = tmp;
2)賦值
數組解構還能夠有選擇性的賦值,只要在數組指定的位置上不提供元素,就能爲其省去解構賦值。下面是一個實現的例子,只爲數組的第3個元素提供變量名,在此以前只有兩個用於佔位的逗號。
[, , z] = [1, 2, 3]; console.log(z); //3
解構賦值表達式的右側必須是可迭代對象,不然就會報錯。下面的語句都是不正確的。
[x, y] = NaN; [x, y] = undefined; [x, y] = null;
前面第2篇中曾用擴展運算符處理數組,但只在賦值表達式的右側使用了擴展運算符。其實,還能夠把擴展運算符放到表達式的左側,以下所示。
[x, ...y] = [1, 2, 3]; console.log(x); //1 console.log(y); //[2,3]
在上面的代碼中,可將...y稱爲剩餘元素。右側數組的第一個元素賦給了x變量,剩下的兩個元素被收集起來賦給了y數組。注意,剩餘元素必須是數組的最後一個元素,而且後面不能有逗號。
1)賦值
因爲對象的屬性沒有按順序排列,因此解構對象只會根據屬性的名稱是否相同來取值。下面代碼的第一行是對象解構的簡寫形式,其效果至關於第二行語句。
({ a, b } = { b: 1, a: 2 });
({ a: a, b: b } = { b: 1, a: 2 });
用上面的代碼來描述對象解構的原理,步驟是:先找到同名屬性(冒號左邊的a或b),而後把各自的屬性值對應起來,等號左側的屬性值(冒號右邊的a或b)表示賦值目標,等號右側的屬性值(1或2)表示要提取的值。由此可知,匹配同名屬性只是爲了定位,真正被賦值的是處在屬性值位置上的變量或另外一個對象的屬性。下面會用不一樣名稱(即應用別名)的變量和屬性來執行對象解構。
({ a: e, b: f } = { b: 5, a: 6 }); console.log(e, f); //6 5 var obj = { e, f }; ({ a: obj.e, b: obj.f } = { b: 5, a: 6 }); console.log(obj.e, obj.f); //6 5
2)屬性
對象解構容許出現多個同名屬性,以下代碼所示,等號左側的對象中雖然包含了兩個a屬性,但兩個變量e和f都被成功賦了值。
({ a: e, a: f } = { b: 5, a: 6 }); console.log(e, f); //6 6
ES6容許對象字面量的屬性名用表達式定義(即屬性名可計算),語法也很簡單,只需將屬性名用方括號包裹。對於這類屬性,對象解構也能正確執行,以下所示。
var obj = { preName: "strick" }, attr = "Name"; ({ ["pre" + attr]: value } = obj); //屬性名是表達式 console.log(value); //"strick"
數組解構和對象解構都能包含一個可供賦值的默認值。若是是數組解構而且指定位置的元素不存在或其值不存在,那麼就會使用默認值。判斷元素的值是否存在,只要與undefined作全等(===)比較,當結果爲真時,表示值不存在。下面的三個賦值表達式都包含了默認值,分別表示三種數組解構的狀況。
[x, y=2] = [1]; console.log(y); //2 [x, y=2] = [1, undefined]; console.log(y); //2 [x, y=2] = [1, null]; console.log(y); //null
1)默認值
對象解構使用默認值的判斷依據是屬性或屬性值是否存在,屬性值與前面的元素值同樣,也要與undefined作全等比較。下面是三種對象解構的狀況,解構條件與上面的數組解構相似。
({ a, b=2 } = { a: 1 }); console.log(b); //2 ({ a, b=2 } = { a: 1, b: undefined }); console.log(b); //2 ({ a, b=2 } = { a: 1, b: null }); console.log(b); //null
上面代碼使用了對象解構的簡寫形式,若是要爲別名變量提供默認值,那麼就要在別名變量右邊加等號,以下所示。
({ a, b: digit=2 } = { a: 1 }); console.log(digit); //2
2)嵌套解構
數組解構和對象解構都支持嵌套解構,這是解構的一種複雜應用,可深刻到嵌套的對象或數組中提取對應的數據,下面是兩種語法的嵌套解構。
[x, [y], z] = [1, [2, 3], 4]; console.log(x, y, z); //1 2 4 ({ a: { b: digit } } = { a: { b: 1 } }); console.log(digit); //1
在上面代碼的第一條語句中,數組的第二個元素也是數組,即一個數組嵌套了另外一個數組。y變量被賦予了內嵌數組的第一個元素,注意,沒有把內嵌數組賦給y變量。在第三條語句中,對象的a屬性值也是對象,即一個對象嵌套了另外一個對象,digit變量被賦予了內嵌對象的b屬性的值。
數組的元素能夠是對象,而對象的屬性也能夠是數組,若是把數組和對象混合在一塊兒,那麼就能夠組成更爲複雜的混合解構,以下所示。
({ a: [b] } = { a: [1] }); console.log(b); //1
參數解構不但擁有前面兩種解構的全部能力,而且能從實參對象中提取數據,賦給函數體中的同名變量。在講解參數解構以前,先來了解一種代碼優化。當函數要接收大量的參數時,爲了增長維護性和擴展性,一般都會用對象代替命名參數,對象的屬性就是函數的參數,以下所示。
function func1(info) { console.log(info.name); console.log(info.age); } func1({ name: "strick", age: 29 });
雖然減小了參數的數量,但同時也下降了可讀性。若是函數沒有爲對象的屬性添加註釋,那麼只能經過閱讀函數體中的代碼來理解其含義。而換成參數解構的函數聲明後,屬性的含義就能一目瞭然,具體以下所示。
function func2({ name, age }) { console.log(name); console.log(age); } func2({ name: "strick", age: 29 });
在上面的代碼中,函數的參數是一個對象,固然,也能夠把參數換成數組,利用第2篇中的剩餘參數來實現相同效果的參數解構,具體以下所示。
function func3(...[name, age]) { console.log(name); console.log(age); } func3("strick", 29);
1)限制
若是參數解構的目標是對象,那麼就會有一個限制。這個限制就是調用函數必須傳遞該對象,不然會拋出異常。注意,下面是錯誤的寫法。
func2();
若要避免拋出異常,可爲參數定義默認值(這是ES6新增的函數特性),以下所示。
function func4({ name, age } = {}) { console.log(name); console.log(age); } func4();
2)默認值對比
當解構默認值和參數默認值結合應用時,二者之間會有一些微妙的差異。下面是兩個函數,func5()函數包含解構默認值,func6()函數包含參數默認值。
function func5({ name = "strick" } = {}) { //解構默認值 console.log(name); } function func6({ name } = { name: "freedom" }) { //參數默認值 console.log(name); }
下面是兩組函數調用的對比,第一組省略了函數的參數,第二組傳入的參數值爲undefined。
func5(); //"strick" func6(); //"freedom" func5(undefined); //"strick" func6(undefined); //"freedom"
根據上面輸出的結果可知,name變量被賦爲解構默認值或參數默認值。這是由於若是函數的參數省略或其值爲undefined,那麼就會使用參數的默認值。因爲func6()函數的參數指定了默認值(即定義了name屬性),所以對象解構後,name變量被賦值爲「freedom」。而func5()函數的參數默認值是空對象,得再根據對象解構默認值的規則才能得出name變量的值,最終name變量會被賦值爲「strick」。
下面是傳入空對象的狀況,兩個函數中的參數都會被賦爲空對象。在對象解構的時候,因爲func5()函數的參數包含解構默認值,所以name變量被賦值爲「strick」;而func6()函數的參數並不包含解構默認值,所以name變量的值爲undefined。
func5({}); //"strick" func6({}); //undefined