1. 程式人生 > >ES6解構賦值

ES6解構賦值

所有 含義 提取 處理 s函數 事情 數據結構 無法 defined

前面的話

  我們經常定義許多對象和數組,然後有組織地從中提取相關的信息片段。在ES6中添加了可以簡化這種任務的新特性:解構。解構是一種打破數據結構,將其拆分為更小部分的過程。本文將詳細介紹ES6解構賦值

引入

  在ES5中,開發者們為了從對象和數組中獲取特定數據並賦值給變量,編寫了許多看起來同質化的代碼

技術分享
let options = {
    repeat: true,
    save: false
};
// 從對象中提取數據
let repeat = options.repeat,
save = options.save;
技術分享

  這段代碼從options對象中提取repeat和save的值,並將其存儲為同名局部變量,提取的過程極為相似

  如果要提取更多變量,則必須依次編寫類似的代碼來為變量賦值,如果其中還包含嵌套結構,只靠遍歷是找不到真實信息的,必須要深入挖掘整個數據結構才能找到所需數據

  所以ES6添加了解構功能,將數據結構打散的過程變得更加簡單,可以從打散後更小的部分中獲取所需信息

對象解構

  對象字面量的語法形式是在一個賦值操作符左邊放置一個對象字面量

技術分享
let node = {
    type: "Identifier",
    name: "foo"
};
let { type, name } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
技術分享

  在這段代碼中,node.type的值被存儲在名為type的變量中;node.name的值被存儲在名為name的變量中

【解構賦值】

  到目前為止,我們已經將對象解構應用到了變量的聲明中。然而,我們同樣可以在給變量賦值時使用解構語法

技術分享
let node = {
    type: "Identifier",
    name: "foo"
},
type = "Literal",
name = 5;
// 使用解構來分配不同的值
({ type, name } = node);
console.log(type); // "Identifier"
console.log(name); // "foo"
技術分享

  在這個示例中,聲明變量type和name時初始化了一個值,在後面幾行中,通過解構賦值的方法,從node對象讀取相應的值重新為這兩個變量賦值

  [註意]一定要用一對小括號包裹解構賦值語句,JS引擎將一對開放的花括號視為一個代碼塊。語法規定,代碼塊語句不允許出現在賦值語句左側,添加小括號後可以將塊語句轉化為一個表達式,從而實現整個解構賦值過程

  解構賦值表達式的值與表達式右側(也就是=右側)的值相等,如此一來,在任何可以使用值的地方都可以使用解構賦值表達式

技術分享
let node = {
    type: "Identifier",
    name: "foo"
},
type = "Literal",
name = 5;
function outputInfo(value) {
    console.log(value === node); // true
}
outputInfo({ type, name } = node);
console.log(type); // "Identifier"
console.log(name); // "foo"
技術分享

  調用outputlnfo()函數時傳入了一個解構表達式,由於JS表達式的值為右側的值,因而此處傳入的參數等同於node,且變量type和name被重新賦值,最終將node傳入outputlnfo()函數

  [註意]解構賦值表達式(也就是=右側的表達式)如果為null或undefined會導致程序拋出錯誤。也就是說,任何嘗試讀取null或undefined的屬性的行為都會觸發運行時錯誤

【默認值】

  使用解構賦值表達式時,如果指定的局部變量名稱在對象中不存在,那麽這個局部變量會被賦值為undefined

技術分享
let node = {
    type: "Identifier",
    name: "foo"
};
let { type, name, value } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // undefined
技術分享

  這段代碼額外定義了一個局部變量value,然後嘗試為它賦值,然而在node對象上,沒有對應名稱的屬性值,所以像預期中的那樣將它賦值為undefined

  當指定的屬性不存在時,可以隨意定義一個默認值,在屬性名稱後添加一個等號(=)和相應的默認值即可

技術分享
let node = {
    type: "Identifier",
    name: "foo"
};
let { type, name, value = true } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value); // true
技術分享

  在此示例中,為變量value設置了默認值true,只有當node上沒有該屬性或者該屬性值為undefined時該值才生效。此處沒有node.value屬性,因為value使用了預設的默認值

【為非同名局部變量賦值】

  如果希望使用不同命名的局部變量來存儲對象屬性的值,ES6中的一個擴展語法可以滿足需求,這個語法與完整的對象字面量屬性初始化程序的很像

技術分享
let node = {
    type: "Identifier",
    name: "foo"
};
let { type: localType, name: localName } = node;
console.log(localType); // "Identifier"
console.log(localName); // "foo"
技術分享

  這段代碼使用了解構賦值來聲明變量localType和localName,這兩個變量分別包含node.type和node.name屬性的值。type:localType語法的含義是讀取名為type的屬性並將其值存儲在變量localType中,這種語法實際上與傳統對象字面量的語法相悖,原來的語法名稱在冒號左邊,值在右邊;現在值在冒號右邊,而對象的屬性名在左邊

  當使用其他變量名進行賦值時也可以添加默認值,只需在變量名後添加等號和默認值即可

技術分享
let node = {
    type: "Identifier"
};
let { type: localType, name: localName = "bar" } = node;
console.log(localType); // "Identifier"
console.log(localName); // "bar"
技術分享

  在這段代碼中,由於node.name屬性不存在,變量被默認賦值為"bar"

【嵌套對象解構】

  解構嵌套對象仍然與對象字面量的語法相似,可以將對象拆解以獲取想要的信息

技術分享
let node = {
    type: "Identifier",
    name: "foo",
    loc: {
        start: {
            line: 1,
            column: 1
        },
    end: {
        line: 1,
        column: 4
    }
}
};
let { loc: { start }} = node;
console.log(start.line); // 1
console.log(start.column); // 1
技術分享

  在這個示例中,我們在解構模式中使用了花括號,其含義為在找到node對象中的loc屬性後,應當深入一層繼續查找start屬性。在上面的解構示例中,所有冒號前的標識符都代表在對象中的檢索位置,其右側為被賦值的變量名;如果冒號後是花括號,則意味著要賦予的最終值嵌套在對象內部更深的層級中

  更進一步,也可以使用一個與對象屬性名不同的局部變量名

技術分享
let node = {
    type: "Identifier",
    name: "foo",
    loc: {
        start: {
            line: 1,
            column: 1
        },
        end: {
            line: 1,
            column: 4
        }
    }
};
// 提取 node.loc.start
let { loc: { start: localStart }} = node;
console.log(localStart.line); // 1
console.log(localStart.column); // 1
技術分享

  在這個版本中,node.loc.start被存儲在了新的局部變量localStart中。解構模式可以應用於任意層級深度的對象,且每一層都具備同等的功能

數組解構

  與對象解構的語法相比,數組解構就簡單多了,它使用的是數組字面量,且解構操作全部在數組內完成,而不是像對象字面量語法一樣使用對象的命名屬性

let colors = [ "red", "green", "blue" ];
let [ firstColor, secondColor ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"

  在這段代碼中,我們從colors數組中解構出了"red"和"green"這兩個值,並分別存儲在變量firstColor和變量secondColor中。在數組解構語法中,我們通過值在數組中的位置進行選取,且可以將其存儲在任意變量中,未顯式聲明的元素都會直接被忽略

  在解構模式中,也可以直接省略元素,只為感興趣的元素提供變量名

let colors = [ "red", "green", "blue" ];
let [ , , thirdColor ] = colors;
console.log(thirdColor); // "blue"

  這段代碼使用解構賦值語法從colors中獲取第3個元素,thirdColor前的逗號是前方元素的占位符,無論數組中的元素有多少個,都可以通過這種方法提取想要的元素,不需要為每一個元素都指定變量名

【解構賦值】

  數組解構也可用於賦值上下文,但不需要用小括號包裹表達式,這一點與對象解構不同

技術分享
let colors = [ "red", "green", "blue" ],
firstColor = "black",
secondColor = "purple";
[ firstColor, secondColor ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"
技術分享

  這段代碼中的解構賦值與上一個數組解構示例相差無幾,唯一的區別是此處的firstColor變量和secondColor變量已經被定義了

【變量交換】

  數組解構語法還有一個獨特的用例:交換兩個變量的值。在排序算法中,值交換是一個非常常見的操作,如果要在ES5中交換兩個變量的值,則須引入第三個臨時變量

技術分享
// 在 ES5 中互換值
let a = 1,
  b = 2,
  tmp;
tmp = a;
a = b;
b = tmp;
console.log(a); // 2
console.log(b); // 1
技術分享

  在這種變量交換的方式中,中間變量tmp不可或缺。如果使用數組解構賦值語法,就不再需要額外的變量了

技術分享
// 在 ES6 中互換值
let a = 1,
    b = 2;
[ a, b ] = [ b, a ];
console.log(a); // 2
console.log(b); // 1
技術分享

  在這個示例中,數組解構賦值看起來像是一個鏡像:賦值語句左側(也就是等號左側)與其他數組解構示例一樣,是一個解構模式;右側是一個為交換過程創建的臨時數組字面量。代碼執行過程中,先解構臨時數組,將b和a的值復制到左側數組的前兩個位置,最終結果是變量互換了它們的值

  [註意]如果右側數組解構賦值表達式的值為null或undefined,則會導致程序拋出錯誤

【默認值】

  也可以在數組解構賦值表達式中為數組中的任意位置添加默認值,當指定位置的屬性不存在或其值為undefined時使用默認值

let colors = [ "red" ];
let [ firstColor, secondColor = "green" ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"

  在這段代碼中,colors數組中只有一個元素,secondColor沒有對應的匹配值,但是它有一個默認值"green",所以最終secondColor的輸出結果不會是undefined

【嵌套數組解構】

  嵌套數組解構與嵌套對象解構的語法類似,在原有的數組模式中插入另一個數組模式,即可將解構過程深入到下一個層級

let colors = [ "red", [ "green", "lightgreen" ], "blue" ];
// 隨後
let [ firstColor, [ secondColor ] ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"

  在此示例中,變量secondColor引用的是colors數組中的值"green",該元素包含在數組內部的另一個數組中,所以seconColor兩側的方括號是一個必要的解構模式。同樣,在數組中也可以無限深入去解構,就像在對象中一樣

【不定元素】

  函數具有不定參數,而在數組解構語法中有一個相似的概念——不定元素。在數組中,可以通過...語法將數組中的其余元素賦值給一個特定的變量

技術分享
let colors = [ "red", "green", "blue" ];
let [ firstColor, ...restColors ] = colors;
console.log(firstColor); // "red"
console.log(restColors.length); // 2
console.log(restColors[0]); // "green"
console.log(restColors[1]); // "blue"
技術分享

  數組colors中的第一個元素被賦值給了firstColor,其余的元素被賦值給restColors數組,所以restColors中包含兩個元素:"green"和"blue"。不定元素語法有助於從數組中提取特定元素並保證其余元素可用

【數組復制】

  在ES5中,開發者們經常使用concat()方法來克隆數組

// 在 ES5 中克隆數組
var colors = [ "red", "green", "blue" ];
var clonedColors = colors.concat();
console.log(clonedColors); //"[red,green,blue]"

  concat()方法的設計初衷是連接兩個數組,如果調用時不傳遞參數就會返回當前函數的副本

  在ES6中,可以通過不定元素的語法來實現相同的目標

// 在 ES6 中克隆數組
let colors = [ "red", "green", "blue" ];
let [ ...clonedColors ] = colors;
console.log(clonedColors); //"[red,green,blue]"

  在這個示例中,我們通過不定元素的語法將colors數組中的值復制到clonedColors數組中

  [註意]在被解構的數組中,不定元素必須為最後一個條目,在後面繼續添加逗號會導致程序拋出語法錯誤

混合解構

  可以混合使用對象解構和數組解構來創建更多復雜的表達式,如此一來,可以從任何混雜著對象和數組的數據解構中提取想要的信息

技術分享
let node = {
    type: "Identifier",
    name: "foo",
    loc: {
        start: {
            line: 1,
            column: 1
        },
        end: {
            line: 1,
            column: 4
        }
    },
    range: [0, 3]
};
let {
    loc: { start },
    range: [ startIndex ]
} = node;
console.log(start.line); // 1
console.log(start.column); // 1
console.log(startIndex); // 0
技術分享

  這段代碼分別將node.loc.start和node.range[0]提取到變量start和startlndex中

  解構模式中的loc和range僅代表它們在node對象中所處的位置(也就是該對象的屬性)。當使用混合解構的語法時,則可以從node提取任意想要的信息。這種方法極為有效,尤其是從JSON配置中提取信息時,不再需要遍歷整個結構了

【解構參數】

  解構可以用在函數參數的傳遞過程中,這種使用方式更特別。當定義一個接受大量可選參數的JS函數時,通常會創建一個可選對象,將額外的參數定義為這個對象的屬性

技術分享
// options 上的屬性表示附加參數
function setCookie(name, value, options) {
    options = options || {};
    let secure = options.secure,
        path = options.path,
        domain = options.domain,
        expires = options.expires;
        // 設置 cookie 的代碼
}
// 第三個參數映射到 options
setCookie("type", "js", {
    secure: true,
    expires: 60000
});
技術分享

  許多JS庫中都有類似的setCookie()函數,而在示例函數中,name和value是必需參數,而secure、path、domain和expires則不然,這些參數相對而言沒有優先級順序,將它們列為額外的命名參數也不合適,此時為options對象設置同名的命名屬性是一個很好的選擇。現在的問題是,僅查看函數的聲明部分,無法辨識函數的預期參數,必須通過閱讀函數體才可以確定所有參數的情況

  如果將options定義為解構參數,則可以更清晰地了解函數預期傳入的參數。解構參數需要使用對象或數組解構模式代替命名參數

技術分享
function setCookie(name, value, { secure, path, domain, expires }) {
// 設置 cookie 的代碼
}
setCookie("type", "js", {
    secure: true,
    expires: 60000
});
技術分享

  這個函數與之前示例中的函數具有相似的特性,只是現在使用解構語法代替了第3個參數來提取必要的信息,其他參數保持不變,但是對於調用setCookie()函數的使用者而言,解構參數變得更清晰了

【必須傳值的解構參數】

  解構參數有一個奇怪的地方,默認情況下,如果調用函數時不提供被解構的參數會導致程序拋出錯誤

// 出錯!
setCookie("type", "js");

  缺失的第3個參數,其值為undefined,而解構參數只是將解構聲明應用在函數參數的一個簡寫方法,其會導致程序拋出錯誤。當調用setCookie()函數時,JS引擎實際上做了以下這些事情

function setCookie(name, value, options) {
    let { secure, path, domain, expires } = options;
    // 設置 cookie 的代碼
}

  如果解構賦值表達式的右值為null或undefined,則程序會報錯。同理,若調用setCookie()函數時不傳入第3個參數,也會導致程序拋出錯誤

  如果解構參數是必需的,大可忽略掉這些問題;但如果希望將解構參數定義為可選的,那麽就必須為其提供默認值來解決這個問題

function setCookie(name, value, { secure, path, domain, expires } = {}) {
    // ...
}

  這個示例中為解構參數添加了一個新對象作為默認值,secure、path、domain及expires這些變量的值全部為undefined,這樣即使在調用setCookie()時未傳遞第3個參數,程序也不會報錯

【默認值】

  可以為解構參數指定默認值,就像在解構賦值語句中那樣,只需在參數後添加等號並且指定一個默認值即可

技術分享
function setCookie(name, value,
    {
        secure = false,
        path = "/",
        domain = "example.com",
        expires = new Date(Date.now() + 360000000)
    } = {}
) {
    // ...
}
技術分享

  在這段代碼中,解構參數的每一個屬性都有默認值,從而無須再逐一檢查每一個屬性是否都有默認值。然而,這種方法也有很多缺點。首先,函數聲明變得比以前復雜了;其次,如果解構參數是可選的,那麽仍然要給它添加一個空對象作為參數,否則像setCookie("type","js")這樣的調用會導致程序拋出錯誤

  對於對象類型的解構參數,最好為其賦予相同解構的默認參數

技術分享
function setCookie(name, value,
    {
        secure = false,
        path = "/",
        domain = "example.com",
        expires = new Date(Date.now() + 360000000)
    } = {
        secure : false,
        path : "/",
        domain : "example.com",
        expires : new Date(Date.now() + 360000000)        
    }
) {
    // ...
}
技術分享

  現在函數變得更加完整了,第一個對象字面量是解構參數,第二個為默認值。但是這會造成非常多的代碼冗余,可以將默認值提取到一個獨立對象中,並且使用該對象作為解構和默認參數的一部分,從而消除這些冗余

技術分享
const setCookieDefaults = {
    secure : false,
    path : "/",
    domain : "example.com",
    expires : new Date(Date.now() + 360000000)    
}
function setCookie(name, value,{
        secure = setCookieDefaults.secure,
        path = setCookieDefaults.path,
        domain = setCookieDefaults.domain,
        expires = setCookieDefaults.expires        
    }=setCookieDefaults) {
    // ...
}
技術分享

  在這段代碼中,默認值已經被放到setCookieDefaults對象中,除了作為默認參數值外,在解構參數中可以直接使用這個對象來為每一個綁定設置默認參數。使用解構參數後,不得不面對處理默認參數的復雜邏輯,但它也有好的一面,如果要改變默認值,可以立即在setCookieDefaults中修改,改變的數據將自動同步到所有出現過的地方

其他解構

【字符串解構】

  字符串也可以解構賦值。這是因為,字符串被轉換成了一個類似數組的對象

技術分享
const [a, b, c, d, e] = ‘hello‘;
console.log(a);//"h"
console.log(b);//"e"
console.log(c);//"l"
console.log(d);//"l"
console.log(e);//"o"
技術分享

  類似數組的對象都有一個length屬性,因此還可以對這個屬性解構賦值

const {length} = ‘hello‘;
console.log(length);//5

【數值和布爾值解構】

  解構賦值時,如果等號右邊是數值和布爾值,則會先轉為對象

let {toString:s1} = 123;
console.log(s1 === Number.prototype.toString);//true
let {toString:s2} = true;
console.log(s2 === Boolean.prototype.toString);//true

  解構賦值的規則是,只要等號右邊的值不是對象或數組,就先將其轉為對象。由於undefinednull無法轉為對象,所以對它們進行解構賦值,都會報錯

let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError

ES6解構賦值