深入ES6 三 解構賦值

解構賦值

解構賦值指的是按照一定規則,直接從數組和對象中提取值,對變量進行賦值。這章主要包括:

  1. 數組的解構賦值
  2. 對象的解構賦值
  3. 嵌套與匹配模式
  4. 應用

3.1 數組的解構賦值

基本用法

解構賦值的本質是模式匹配,只要左右兩邊的模式相同,左邊的變量就會被賦予對應的值。如果解構不成功,則返回undefined。

let [d,b,c] = [1,2,3]; // => d=1;b=2;c=3
let [[f]] = [[1]];  // =>f=1
let [e,...tail] = [1,2,3,4]; // =>e=1;tail = [2,3,4]

這裏let [a, b, c]表示定義了用於數組解構的三個變量a, b, c。解構採取貪婪匹配原則,儘量解構更多的數據,並且即使模式不是完全匹配,也能部分解構成功。上面的rest(…)運算符會收錄匹配到的其餘數據進一個數組,但其必須出現在末尾,否則會報錯。

注意:採用結構賦值的寫法,變量的聲明和賦值是一體的,對於let和const而言,變量不得重新聲明,否則會報錯。

let [a, b, c] = [1, 2, 3];
let a = 10; // 報錯,因爲a已經聲明過了

再介紹一個乍一眼看上去容易出錯的例子:

let [a, [b], c] = [1, [2, 3], 4]; // b=>2
// 如果想讓b的值解構後爲[2,3],需要寫成
let [a,[...b],c] = [1, [2, 3], 4];
// 或者最簡單的
let [a, b, c] = [1,[2,3],4]

右值的要求

在進行對數組的解構賦值時,等號右邊必須是可遍歷的解構,比如說數組,Set集合,甚至Generator對象(會在面的章節介紹)。否則會報錯。

字符串在解構時會被轉化成一個類似數組的對象,因此可以用於數組的解構賦值

let [a, b, c] = 'hello' // => a=h; b=e; c=l

字符串擁有length屬性,我們也可以使用對象的解構賦值來獲得之,此時字符串hello會被包裝成一個對象:

let {length} = 'hello'

默認值

可以指定解構賦值時的默認值,當右值數組成員嚴格等於undefined時,默認值纔會生效。

let [a = 'hello'] = []; //a='hello'
let [a = 'hello'] = [null]; //a=null

如果默認值是表達式,它的求值方式是惰性的,只有需要使用默認值的時候,才進行計算

let foo = () => {
    console.log('fooing');
    return 'foo'
};
let [b = foo()] = [];  // 會執行foo函數
let [b = foo()] = ['bar']; // 不會執行foo函數

默認值也可以引用解構變量的其它值,但引用的值必須在引用之前被聲明

let [x, y = x] = [1]

2.2 對象的解構賦值

基本用法

let {c, d} = {c:'c,  e:'e',  d:'d'};
c //=> 'c'
d // => 'd'

與數組解構的一個不同之處在於對象解構把[]換成了{}, let {c, d} 定義了兩個全局變量:c和d。此外,對象解構賦值是和對象屬性的定義順序無關的,只要名稱能夠匹配,即左邊的變量名與右邊的屬性名相同,就可以取得正確的值。爲了告訴你們這點,我特意把e安排到了d前面。

很多時候我們不希望我們的局部變量名和屬性名完全相同,那麼可以採取下面的辦法:

let {foo:baz} = {foo:'foo',bar:'bar'}; // => baz=foo

格式是:

{屬性名:變量名... ...}={屬性名:值...}

也就是說只需要在屬性名後面加”:希望的變量名”就可以了。

那麼,你肯定能夠想到,我們開始時寫的這種形式:

let {c, d} = {c:'c, e:'e' ,d:'d'};

其實是下面形式的簡寫,因爲它們的屬性名和變量名是相同的。真正被賦值的其實是後者:

let {c:c, d:d} = {c:'c, e:'e', d:'d'};

使用()告訴引擎我在用解構賦值

如果一個已經聲明的變量進行解構賦值,大概會像下面這樣:

let foo;
{foo} = {foo:'foo'} // 報錯

這是因爲大括號寫在了首行,JS引擎會把它當成一個代碼塊,解決辦法很簡單,加上()就可以了:

({foo} = {foo:'foo'})

注意,除了這裏以及函數表達式調用的時候可能要用到(),我們使用解構賦值的時候儘量不要使用(),因爲它幾乎沒有用處,還容易導致各種錯誤出現

默認值

前面提到過的

let {foo:foo,bar:baz} = {foo:'foo',bar:'bar'}

在進行解構賦值時,實際上是先通過左邊的foo,bar屬性名去到右邊匹配屬性,然後把匹配到的屬性的值賦給左邊的:後的變量名。那麼你就不難想到,對象解構可以像下面這樣設置默認值:

let {foo='bbb',bar:baz='aaa'} = {foo:'foo',bar:'bar'}

上面的代碼foo代表屬性名和變量名相同的情況,baz代表不同的情況。

右值的要求

使用對象解構賦值時,右值可以是簡單數據類型布爾、字串或者數字。解構時,會先將其轉化爲對象,再進行解構。

右值不得是不能轉成對象的null或undefined,因爲它們沒有對應的包裝類。

對象嵌套的陷阱

如果在解構模式中左值是對象嵌套對象的形式,且外部對象屬性沒有正確匹配,則在匹配該對象的內部屬性時將會報錯:

var {foo:{bar}} = {baz:'baz'}

上面的代碼中,等號左邊對象的foo屬性無法正確匹配,所以值爲undefined。當繼續匹配其中的bar屬性時,就會報錯,因爲我們不能從undefined中取子屬性。

2.3 嵌套解構與匹配模式

解構賦值本身非常靈活,所以可以進行匹配模式的嵌套:

let {arr:[x,{prop}]} = {arr:[1,{prop:2}]};
console.log(x); // =>1
console.log(prop); // =>2
console.log(arr); // 報錯,未定義

上面代碼中的arr是匹配模式的一部分,所以不會被賦值。

2.4 應用

這裏舉幾個解構賦值非常常見的應用:

交換兩個變量的值

[foo,bar] = [bar,foo];

定義函數的參數

var foo = ([x, y])=>x+y;
foo([1,2]) // =>3

或者以對象的形式,這樣可以優化以配置對象形式編寫函數參數方式的代碼

var bar = ({x, y, z=5}) => x-y+z
bar({x:1, y:2}) // => 4

但需要注意的是,不要把解構賦值默認參數與ES6函數本身默認參數的特性相混淆。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章