第二章:語(yǔ)法 (2/5) -《你不知道的JavaScript:ES6 & Beyond》

解構(gòu)(Destructuring)

ES6引入了一個(gè)新的語(yǔ)法特性垢夹,叫做解構(gòu),這可能會(huì)和結(jié)構(gòu)化賦值的概念有點(diǎn)混淆铆铆。為了方便大家理解窝撵,考慮如下代碼:

function foo() {
    return [1,2,3];
}

var tmp = foo(),
    a = tmp[0], b = tmp[1], c = tmp[2];

console.log( a, b, c );             // 1 2 3

如你所見(jiàn)傀顾,我們手動(dòng)地把foo()返回的數(shù)組分別賦值給了ab忿族、c锣笨,這樣做我們必須(無(wú)可奈何地)引入變量tmp

同樣地道批,我們也可以用對(duì)象來(lái)實(shí)現(xiàn):

function bar() {
    return {
        x: 4,
        y: 5,
        z: 6
    };
}

var tmp = bar(),
    x = tmp.x, y = tmp.y, z = tmp.z;

console.log( x, y, z );             // 4 5 6

屬性值tmp.x被賦值給了變量x错英,同樣地,y得到的是tmp.y隆豹,ztmp.z椭岩。

手動(dòng)給數(shù)組的索引值或?qū)ο蟮膶傩灾蒂x值的方式被稱(chēng)為結(jié)構(gòu)化賦值。ES6為此專(zhuān)門(mén)引入了新的語(yǔ)法璃赡,特別為數(shù)組解構(gòu)對(duì)象解構(gòu)判哥。有了這個(gè)語(yǔ)法我們就不需要前面代碼中的tmp變量了,可以讓代碼看起來(lái)更清晰碉考∷疲考慮:

var [ a, b, c ] = foo();
var { x: x, y: y, z: z } = bar();

console.log( a, b, c );             // 1 2 3
console.log( x, y, z );             // 4 5 6

在賦值的時(shí)候,你可能更習(xí)慣看到[a, b, c]這樣的語(yǔ)句位于=右邊侯谁,锌仅。

解構(gòu)對(duì)稱(chēng)地翻轉(zhuǎn)了這個(gè)模式,所以在=賦值左邊的[a, b, c]被認(rèn)為是用來(lái)把右手邊數(shù)組里的值相應(yīng)地賦值給左邊的變量墙贱。

類(lèi)似地热芹,{ x: x, y: y, z: z }特指從bar()解構(gòu)對(duì)象的值從而賦值到多個(gè)變量中的這種方式。

對(duì)象屬性賦值模式

讓我們來(lái)深入前面代碼中的{ x: x, .. }語(yǔ)法惨撇。如果屬性的名字和你要聲明的變量名一致伊脓,你可以把它們簡(jiǎn)寫(xiě)為:

var { x, y, z } = bar();

console.log( x, y, z );             // 4 5 6

還不錯(cuò)吧?

但是{ x, ..}去掉的是x:還是: x部分呢魁衙?當(dāng)我們使用簡(jiǎn)寫(xiě)的時(shí)候报腔,實(shí)際上是省略了x:株搔。這似乎是個(gè)微不足道的細(xì)節(jié),但你馬上就會(huì)看到它的重要性榄笙。

如果可以簡(jiǎn)寫(xiě)邪狞,那我們有什么理由再去用啰嗦的方式呢?原因是這種長(zhǎng)的形式允許我們用不同的變量名給屬性賦值茅撞,有時(shí)候還是挺有用的:

var { x: bam, y: baz, z: bap } = bar();

console.log( bam, baz, bap );       // 4 5 6
console.log( x, y, z );             // ReferenceError

這里有個(gè)很微妙卻非常重要的技巧來(lái)理解這個(gè)對(duì)象解構(gòu)形式的變體。為了說(shuō)明這是個(gè)需要提防的陷阱巨朦,讓我們來(lái)看一般的對(duì)象賦值是怎么做的:

var X = 10, Y = 20;

var o = { a: X, b: Y };

console.log( o.a, o.b );            // 10 20

{ a: X, b: Y }里米丘,我們知道a是對(duì)象屬性,X是用來(lái)賦值的數(shù)據(jù)源糊啡。換句話(huà)說(shuō)拄查,這個(gè)語(yǔ)法的模式是target: source,說(shuō)白了棚蓄,就是property-alias: value堕扶。直覺(jué)告訴我們這和=賦值的用法是一樣的,就像target = source梭依。

然而在使用對(duì)象解構(gòu)賦值的時(shí)候——也就是把看起來(lái)像對(duì)象一樣的{ .. }語(yǔ)法放在了=左邊的時(shí)候——其實(shí)我們是把target: source這個(gè)語(yǔ)法倒置了稍算。

回想:

var { x: bam, y: baz, z: bap } = bar();

這里的語(yǔ)法模式是source: target(或value: variable-alias)。x: bam的意思是屬性x是數(shù)據(jù)源役拴,bam是賦值的目標(biāo)變量糊探。換句話(huà)說(shuō),對(duì)象語(yǔ)法是target <-- source河闰,而對(duì)象解構(gòu)賦值是source --> target科平。看到這里的翻轉(zhuǎn)了姜性?

還有另外一種方式來(lái)理解這個(gè)語(yǔ)法瞪慧,可能更清楚一點(diǎn)〔磕睿考慮:

var aa = 10, bb = 20;

var o = { x: aa, y: bb };
var     { x: AA, y: BB } = o;

console.log( AA, BB );              // 10 20

{ x: aa, y: bb }這行弃酌,xy代表對(duì)象屬性。{ x: AA, y: BB }這行印机,矢腻,xy代表對(duì)象屬性。

回想之前我是如何斷定{ x, .. }丟掉了x:部分嗎射赛?在那兩行代碼里多柑,如果你去掉x:y:兩部分,你會(huì)只剩下aa, bbAA, BB楣责,實(shí)際上——只是理論上竣灌,不是真的——是把aa賦值給BB以及bb賦值給BB聂沙。

這種對(duì)稱(chēng)性可以幫助我們更好的理解為什么這種語(yǔ)法形式在ES6里被倒置了。

注意:我可能還是傾向于用{ AA: x , BB: y }來(lái)表示解構(gòu)語(yǔ)法初嘹,這樣可以和之前的target: source保持一直及汉。可惜的是我得訓(xùn)練自己的大腦來(lái)習(xí)慣這個(gè)顛倒的形式屯烦,相信很多讀者也是坷随。

不僅僅是聲明

到目前為之,我們都是用var來(lái)聲明解構(gòu)賦值(當(dāng)然驻龟,你也可以用letconst)温眉。但解構(gòu)是一個(gè)通用的操作,并不僅僅是聲明翁狐。

考慮:

var a, b, c, x, y, z;

[a,b,c] = foo();
( { x, y, z } = bar() );

console.log( a, b, c );             // 1 2 3
console.log( x, y, z );             // 4 5 6

變量可以是聲明過(guò)的类溢,解構(gòu)只做了賦值操作,正如我們所見(jiàn)露懒。

注意:對(duì)于對(duì)象解構(gòu)形式闯冷,如果沒(méi)有var/let/const,就必須要用( )把整個(gè)賦值表達(dá)式包起來(lái)懈词,不然表達(dá)式左手邊的第一個(gè){ .. }就會(huì)被當(dāng)成塊聲明蛇耀,而不是對(duì)象。

事實(shí)上钦睡,賦值表達(dá)式(a, y等等)并不只能是變量標(biāo)識(shí)符蒂窒。任何合法的賦值表達(dá)式都可以,例如:

var o = {};

[o.a, o.b, o.c] = foo();
( { x: o.x, y: o.y, z: o.z } = bar() );

console.log( o.a, o.b, o.c );       // 1 2 3
console.log( o.x, o.y, o.z );       // 4 5 6

你還可以用計(jì)算過(guò)的(變量)屬性表達(dá)式進(jìn)行解構(gòu)荞怒∪髯粒考慮:

var which = "x",
    o = {};

( { [which]: o[which] } = bar() );

console.log( o.x );                 // 4

[which]:部分是計(jì)算出來(lái)的屬性,得到的是x——把對(duì)象中解構(gòu)出的屬性當(dāng)作賦值的源數(shù)據(jù)褐桌。o[which]部分就是一個(gè)正常的對(duì)象key引用衰抑,和o.x等價(jià),作為賦值目標(biāo)荧嵌。

你可以用一般的賦值方法來(lái)創(chuàng)建對(duì)象關(guān)聯(lián)或轉(zhuǎn)型呛踊,例如:

var o1 = { a: 1, b: 2, c: 3 },
    o2 = {};

( { a: o2.x, b: o2.y, c: o2.z } = o1 );

console.log( o2.x, o2.y, o2.z );    // 1 2 3

你也可以把對(duì)象映射成數(shù)組,比如:

var o1 = { a: 1, b: 2, c: 3 },
    a2 = [];

( { a: a2[0], b: a2[1], c: a2[2] } = o1 );

console.log( a2 );                  // [1,2,3]

或者反過(guò)來(lái):

var a1 = [ 1, 2, 3 ],
    o2 = {};

[ o2.a, o2.b, o2.c ] = a1;

console.log( o2.a, o2.b, o2.c );    // 1 2 3

也可以把數(shù)組重新排序變成另外一個(gè)數(shù)組:

var a1 = [ 1, 2, 3 ],
    a2 = [];

[ a2[2], a2[0], a2[1] ] = a1;

console.log( a2 );                  // [2,3,1]

甚至可以不用臨時(shí)變量就解決傳統(tǒng)的“交換變量值”問(wèn)題:

var x = 10, y = 20;

[ y, x ] = [ x, y ];

console.log( x, y );                // 20 10

警告:小心:你不應(yīng)該把賦值和聲明混在一起啦撮,除非你有意讓這些賦值表達(dá)式同時(shí)做為聲明谭网。不然的話(huà),你會(huì)看到語(yǔ)法錯(cuò)誤赃春。這也是為什么在前面的例子里我必須得先聲明var a2 = []愉择,然后才能使用解構(gòu)賦值[ a2[0], .. ] = ..。嘗試var [ a2[0], .. ] = ..這樣做也沒(méi)有任何意義,因?yàn)?code>a2[0]都不是一個(gè)有效的聲明標(biāo)識(shí)符锥涕;顯然它也沒(méi)辦法創(chuàng)建var a2 = []聲明給我們用衷戈。

重復(fù)賦值

對(duì)象解構(gòu)語(yǔ)法允許一個(gè)源屬性(帶有任何值類(lèi)型)多次出現(xiàn)。例如:

var { a: X, a: Y } = { a: 1 };

X;  // 1
Y;  // 1

這也意味著你可以解構(gòu)一個(gè)子對(duì)象/數(shù)組的屬性层坠,同時(shí)拿到這個(gè)子對(duì)象/數(shù)組本身殖妇。考慮:

var { a: { x: X, x: Y }, a } = { a: { x: 1 } };

X;  // 1
Y;  // 1
a;  // { x: 1 }

( { a: X, a: Y, a: [ Z ] } = { a: [ 1 ] } );

X.push( 2 );
Y[0] = 10;

X;  // [10,2]
Y;  // [10,2]
Z;  // 1

關(guān)于解構(gòu)破花,還是要提一句:雖然把所有解構(gòu)賦值放在一行代碼里看起來(lái)不錯(cuò)谦趣,在我的示例里也是這也做的。但是為了可讀性起見(jiàn)座每,更好的辦法是采用多行賦值蔚润,并加上合理的縮進(jìn)——就像JSON或者是對(duì)象值一樣。

// 很難讀:
var { a: { b: [ c, d ], e: { f } }, g } = obj;

// 好一些:
var {
    a: {
        b: [ c, d ],
        e: { f }
    },
    g
} = obj;

記壮咂堋:**解構(gòu)賦值存在的意義不是節(jié)省你敲代碼的功夫,而是更好的可讀性烦租。

解構(gòu)賦值表達(dá)式

帶有對(duì)象或數(shù)組解構(gòu)的賦值表達(dá)式延赌,可以得等式右手邊的對(duì)象/數(shù)組完整的值〔娉鳎考慮:

var o = { a:1, b:2, c:3 },
    a, b, c, p;

p = { a, b, c } = o;

console.log( a, b, c );         // 1 2 3
p === o;                        // true

在這段代碼中挫以,p賦值給了對(duì)象o的引用,而不是a, b, c中的一個(gè)窃祝。下面的數(shù)組解構(gòu)也一樣:

var o = [1,2,3],
    a, b, c, p;

p = [ a, b, c ] = o;

console.log( a, b, c );         // 1 2 3
p === o;                        // true

通過(guò)傳遞完整的對(duì)象/數(shù)組值掐松,你可以把解構(gòu)賦值表達(dá)式串起來(lái):

var o = { a:1, b:2, c:3 },
    p = [4,5,6],
    a, b, c, x, y, z;

( {a} = {b,c} = o );
[x,y] = [z] = p;

console.log( a, b, c );         // 1 2 3
console.log( x, y, z );         // 4 5 4

不多不少剛剛好

無(wú)論是數(shù)組解構(gòu)還是對(duì)象解構(gòu)賦值,你都不需要把所有出現(xiàn)的值都賦上:例如:

function foo() {
    return [1,2,3];
}

function bar() {
    return {
        x: 4,
        y: 5,
        z: 6
    };
}

var [,b] = foo();
var { x, z } = bar();

console.log( b, x, z );             // 2 4 6

foo()返回的13被拋棄了粪小,bar()返回的5也是大磺。

類(lèi)似地,如果你想賦值的變量比出現(xiàn)的變量還多探膊,那么你會(huì)優(yōu)雅地回落到備用的undefined杠愧,正如你所期待的:

var [,,c,d] = foo();
var { w, z } = bar();

console.log( c, z );                // 3 6
console.log( d, w );                // undefined undefined

這個(gè)行為也相應(yīng)地遵從前面提到過(guò)的“找不到undefined”原則。

在本章的前面我們已經(jīng)研究過(guò)了操作符...逞壁,也了解了它有時(shí)可以用來(lái)把數(shù)組里的值展開(kāi)成單獨(dú)的值流济,有時(shí)可以用它做相反的事情:把一組值聚合成一個(gè)數(shù)組。

除了在函數(shù)聲明里可以聚合參數(shù)腌闯,...在解構(gòu)賦值時(shí)也可以做同樣的事情绳瘟。為了清楚地說(shuō)明,我們回憶一下早前的一段代碼:

var a = [2,3,4];
var b = [ 1, ...a, 5 ];

console.log( b );                   // [1,2,3,4,5]

這里我們可以看到...a展開(kāi)了a姿骏,因?yàn)樗挥跀?shù)組[ .. ]值中間糖声。如果...a出現(xiàn)在數(shù)組解構(gòu)里,它則會(huì)表現(xiàn)出聚合:

var a = [2,3,4];
var [ b, ...c ] = a;

console.log( b, c );                // 2 [3,4]

var [ .. ] = aa解構(gòu)成[ .. ]里面的結(jié)構(gòu)。第一部分叫做b對(duì)應(yīng)的是a的第一個(gè)值2姨丈。然后...c聚合其余的值(34)放到叫做c的數(shù)組里畅卓。

注意:我們見(jiàn)過(guò)...在數(shù)組里是如何工作的,但對(duì)象呢蟋恬?它并不是一個(gè)ES6的特性翁潘,不過(guò)可以參考第八章里討論的“beyond ES6”特性,...也可以用來(lái)聚合或者展開(kāi)對(duì)象歼争。

默認(rèn)值賦值

這兩種解構(gòu)賦值形式都支持使用默認(rèn)值拜马,和函數(shù)參數(shù)默認(rèn)值一樣,用=就可以了

考慮:

var [ a = 3, b = 6, c = 9, d = 12 ] = foo();
var { x = 5, y = 10, z = 15, w = 20 } = bar();

console.log( a, b, c, d );          // 1 2 3 12
console.log( x, y, z, w );          // 4 5 6 20

你還可以同時(shí)使用默認(rèn)值賦值和前面提到的選擇性賦值表達(dá)式沐绒,例如:

var { x, y, z, w: WW = 20 } = bar();

console.log( x, y, z, WW );         // 4 5 6 20

在對(duì)象或數(shù)組解構(gòu)賦值使用默認(rèn)值的時(shí)候俩莽,小心不要把自己繞暈(還有讀你代碼的人)。你可能會(huì)寫(xiě)出非常難懂的代碼:

var x = 200, y = 300, z = 100;
var o1 = { x: { y: 42 }, z: { y: z } };

( { y: x = { y: y } } = o1 );
( { z: y = { y: z } } = o1 );
( { x: z = { y: x } } = o1 );

你能得出最后x, yz最后的值是什么嗎乔遮?我可以想象這需要花點(diǎn)時(shí)間來(lái)想明白扮超。我來(lái)揭開(kāi)謎底:

console.log( x.y, y.y, z.y );       // 300 100 42

這里最大的啟發(fā)就是:解構(gòu)很棒很有用,但如果濫用它的話(huà)蹋肮,可能會(huì)很傷腦筋了出刷。

嵌套解構(gòu)

如果解構(gòu)的值有嵌套的對(duì)象或數(shù)組,你也可以解構(gòu)這些嵌套的值:

var a1 = [ 1, [2, 3, 4], 5 ];
var o1 = { x: { y: { z: 6 } } };

var [ a, [ b, c, d ], e ] = a1;
var { x: { y: { z: w } } } = o1;

console.log( a, b, c, d, e );       // 1 2 3 4 5
console.log( w );                   // 6

嵌套解構(gòu)可以作為平鋪對(duì)象命名空間平鋪的簡(jiǎn)單方式坯辩,例如:

var App = {
    model: {
        User: function(){ .. }
    }
};

// instead of:
// var User = App.model.User;

var { model: { User } } = App;

參數(shù)解構(gòu)

你能找到下面這段代碼里的賦值嗎馁龟?

function foo(x) {
    console.log( x );
}

foo( 42 );

這個(gè)賦值有點(diǎn)隱蔽:當(dāng)foo(42)被調(diào)用的時(shí)候,42(參數(shù))被賦值給了x漆魔。如果參數(shù)對(duì)是一個(gè)賦值坷檩,它就說(shuō)明了賦值可以被解構(gòu),對(duì)吧改抡?當(dāng)然啦矢炼!

考慮這種數(shù)組解構(gòu)參數(shù):

function foo( [ x, y ] ) {
    console.log( x, y );
}

foo( [ 1, 2 ] );                    // 1 2
foo( [ 1 ] );                       // 1 undefined
foo( [] );                          // undefined undefined

對(duì)象解構(gòu)參數(shù)也可以:

function foo( { x, y } ) {
    console.log( x, y );
}

foo( { y: 1, x: 2 } );              // 2 1
foo( { y: 42 } );                   // undefined 42
foo( {} );                          // undefined undefined

這種方式和命名參數(shù)很類(lèi)似(被要求加入JS特性很久了!)雀摘,在這里對(duì)象上的屬性映射為相同名字的解構(gòu)參數(shù)裸删。這也意味著我們也順便拿到了可選參數(shù)(在任何位置),如你所見(jiàn)到的阵赠,缺失的參數(shù)x和我們預(yù)想的行為一致涯塔。

當(dāng)然,前面所討論的各種解構(gòu)形式都適用于參數(shù)解構(gòu)清蚀,包括嵌套解構(gòu)匕荸、默認(rèn)值等等。解構(gòu)也適用于其他的ES6函數(shù)參數(shù)行為枷邪,比如參數(shù)默認(rèn)值和展開(kāi)/聚合參數(shù)榛搔。

參考下面的一些簡(jiǎn)要說(shuō)明(當(dāng)然沒(méi)辦法窮盡所有可能):

function f1([ x=2, y=3, z ]) { .. }
function f2([ x, y, ...z], w) { .. }
function f3([ x, y, ...z], ...w) { .. }

function f4({ x: X, y }) { .. }
function f5({ x: X = 10, y = 20 }) { .. }
function f6({ x = 10 } = {}, { y } = { y: 10 }) { .. }

我們從上面的例子中拿一個(gè)來(lái)詳細(xì)說(shuō)明:

function f3([ x, y, ...z], ...w) {
    console.log( x, y, z, w );
}

f3( [] );                           // undefined undefined [] []
f3( [1,2,3,4], 5, 6 );              // 1 2 [3,4] [5,6]

這里有兩個(gè)...操作符,它們都在聚合數(shù)組里的值(zw),然后...z聚合了第一個(gè)數(shù)組參數(shù)剩下的參數(shù)践惑,而...w聚合了主參數(shù)除了第一個(gè)參數(shù)以外的參數(shù)腹泌。

解構(gòu)默認(rèn)值 + 參數(shù)默認(rèn)值

有一個(gè)很微妙的地方你需要注意——解構(gòu)默認(rèn)值和函數(shù)參數(shù)默認(rèn)值的行為是有不同之處的。例如:

function f6({ x = 10 } = {}, { y } = { y: 10 }) {
    console.log( x, y );
}

f6();                               // 10 10

首先尔觉,看起來(lái)好像我們給xy都設(shè)置了默認(rèn)值10凉袱,只是用了兩種不同的方式。然而侦铜,這兩種不同的方式在特定的場(chǎng)景下會(huì)有不同的行為专甩,并且非常微妙。

Consider:

f6( {}, {} );                       // 10 undefined

等等钉稍,為什么會(huì)這樣涤躲?很明顯,命名為x的參數(shù)在第一個(gè)參數(shù)沒(méi)有相同名字的屬性時(shí)贡未,會(huì)使用默認(rèn)值10种樱。

但為什么yundefined呢?對(duì)象值{ y: 10 }是一個(gè)函數(shù)參數(shù)默認(rèn)值俊卤,而不是解構(gòu)默認(rèn)值缸托。因此,它只會(huì)在沒(méi)傳第二個(gè)參數(shù)的時(shí)候起作用瘾蛋,或是undefined的時(shí)候。

在前面的代碼中矫限,我們了第二過(guò)參數(shù)({})哺哼,所以默認(rèn)的{ y: 10 }并沒(méi)有起作用惧磺,而{ y }在嘗試解構(gòu)空對(duì)象{}反璃。

現(xiàn)在,比較一下{ y } = { y: 10 }{ x = 10 } = {}沸枯。

對(duì)于x這種使用方式來(lái)說(shuō)无宿,如果沒(méi)有傳第一個(gè)函數(shù)參數(shù)或者是undefined茵汰,就會(huì)使用空對(duì)象{}。然后孽鸡,無(wú)論什么第一個(gè)傳進(jìn)來(lái)的是什么值——無(wú)論是默認(rèn)值{}還是傳進(jìn)去的任何東西——會(huì)用表達(dá)式{ x = 10 }來(lái)解構(gòu)蹂午,它會(huì)檢查屬性x是否存在,如果沒(méi)有(或者是undefined)彬碱,參數(shù)x就會(huì)使用默認(rèn)值10豆胸。

深呼吸。把前面幾段反復(fù)讀幾次巷疼,然后我們?cè)賮?lái)看下面的代碼:

function f6({ x = 10 } = {}, { y } = { y: 10 }) {
    console.log( x, y );
}

f6();                               // 10 10
f6( undefined, undefined );         // 10 10
f6( {}, undefined );                // 10 10

f6( {}, {} );                       // 10 undefined
f6( undefined, {} );                // 10 undefined

f6( { x: 2 }, { y: 3 } );           // 2 3

可能大多數(shù)人會(huì)認(rèn)為和y的行為相比晚胡,參數(shù)x的行為會(huì)更合理一些。因此,理解這兩種形式為什么會(huì)有差異估盘,以及差異在哪里就顯得很重要瓷患。

如果你還是有點(diǎn)懵就把上面的內(nèi)容再看一遍,自己也玩一下遣妥。未來(lái)的你會(huì)感激你現(xiàn)在花時(shí)間把這個(gè)東西理解透了的擅编。

嵌套默認(rèn)值:解構(gòu)和重組

這種方式乍看上去很難懂,但最近很流行這樣做燥透,用嵌套的對(duì)象屬性來(lái)設(shè)默認(rèn)值:和重組(我這么叫)一起使用對(duì)象解構(gòu)沙咏。

考慮一組嵌套對(duì)象解構(gòu)默認(rèn)值,如下:

// 見(jiàn)http://es-discourse.com/t/partial-default-arguments/120/7

var defaults = {
    options: {
        remove: true,
        enable: false,
        instance: {}
    },
    log: {
        warn: true,
        error: true
    }
};

假如你有個(gè)對(duì)象叫config班套,部分符合上面的結(jié)構(gòu)肢藐,但可能不是全部;你想要把所有缺失的部分設(shè)置默認(rèn)值吱韭,但又不覆蓋已有的設(shè)置:

var config = {
    options: {
        remove: false,
        instance: null
    }
};

你可以像過(guò)去一樣手動(dòng)實(shí)現(xiàn)它:

config.options = config.options || {};
config.options.remove = (config.options.remove !== undefined) ?
    config.options.remove : defaults.options.remove;
config.options.enable = (config.options.enable !== undefined) ?
    config.options.enable : defaults.options.enable;
...

Eww吆豹。

也有些人傾向于用賦值-覆蓋方式來(lái)做。你可能會(huì)被ES6的Object.assign(..)方法(見(jiàn)第六章)先來(lái)克隆defaults的屬性然后再用config的值來(lái)覆蓋理盆,像下面這樣:

config = Object.assign( {}, defaults, config );

看起來(lái)好一點(diǎn)吧痘煤?但這里有個(gè)嚴(yán)重的問(wèn)題!Object.assign(..)是淺賦值猿规,也就是說(shuō)當(dāng)它復(fù)制defaults.options的時(shí)候衷快,只賦值了對(duì)象引用,沒(méi)有進(jìn)一步把對(duì)象屬性克隆到config.options對(duì)象姨俩。Object.assign(..)需要在對(duì)象屬性樹(shù)的每一層(像遞歸一樣)都調(diào)用一下才能實(shí)現(xiàn)你想要的深度克隆蘸拔。

注意:很多JS的方法庫(kù)/框架都提供他們自己的深度克隆對(duì)象方法,但是他們的做法不在我們的討論范圍之內(nèi)环葵。

我們來(lái)看一下ES6帶有默認(rèn)值的對(duì)象解構(gòu)能不能解決這個(gè)麻煩:

config.options = config.options || {};
config.log = config.log || {};
({
    options: {
        remove: config.options.remove = defaults.options.remove,
        enable: config.options.enable = defaults.options.enable,
        instance: config.options.instance = defaults.options.instance
    } = {},
    log: {
        warn: config.log.warn = defaults.log.warn,
        error: config.log.error = defaults.log.error
    } = {}
} = config);

表面上并沒(méi)有Object.assign(..)這樣虛假的承諾那么好看(但它僅僅是淺賦值)调窍,但我覺(jué)得比起手動(dòng)賦值還是好一點(diǎn)。盡管看起來(lái)還是又臭又長(zhǎng)张遭。

前面代碼中的方式可以工作邓萨,因?yàn)槲襤ack了解構(gòu)和默認(rèn)值的機(jī)制,來(lái)檢查屬性是不是=== undefined然后決定是否賦值菊卷。我在解構(gòu)config的同時(shí)又把解構(gòu)出來(lái)的值重新賦給了config缔恳,通過(guò)config.options.enable賦值引用(見(jiàn)代碼最后的= config)。

然而還是太麻煩了洁闰。我們來(lái)看看還可以怎么改進(jìn)褐耳。

下面這種做法是我們認(rèn)為做好的,如果你清楚地知道你在解構(gòu)的各種屬性都有唯一的命名渴庆。如果不是這樣的話(huà)铃芦,你也可以這么做雅镊,但看起來(lái)就沒(méi)那么好看了——你不得不分階段的解構(gòu),或者給那些重復(fù)的命名創(chuàng)建臨時(shí)唯一變量刃滓。

如果我們完全解構(gòu)所有頂層變量仁烹,我們隨即就可以重組還原這個(gè)嵌套的對(duì)象解構(gòu)。

但是所有這些臨時(shí)變量就會(huì)縈繞在周?chē)廴咀饔糜蜻只ⅰK宰跨郑覀冞€是用塊作用域{ }的方式把這部分包起來(lái)(見(jiàn)本章前面的“塊作用域”部分)。

// 把`defaults`并入`config`
{
    // 解構(gòu)(用默認(rèn)值賦值方式)
    let {
        options: {
            remove = defaults.options.remove,
            enable = defaults.options.enable,
            instance = defaults.options.instance
        } = {},
        log: {
            warn = defaults.log.warn,
            error = defaults.log.error
        } = {}
    } = config;

    // 重組
    config = {
        options: { remove, enable, instance },
        log: { warn, error }
    };
}

看起來(lái)好一點(diǎn)了吧砰诵?

注意:你也可以用立即執(zhí)行箭頭函數(shù)來(lái)替代塊作用域的{ }let聲明征唬。這樣的話(huà),解構(gòu)賦值/默認(rèn)值需要在參數(shù)列表里茁彭,以及重組的結(jié)果需要在函數(shù)體中返回(return)总寒。

在重組部分的{ warn, error }可能對(duì)你來(lái)說(shuō)有點(diǎn)眼生,這種形式叫做“簡(jiǎn)明屬性”理肺,我們會(huì)在下一節(jié)詳細(xì)講它摄闸。


該系列文章翻譯自Kyle Simpson的《You don't know about Javascript》,本章原文在此妹萨。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末年枕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子乎完,更是在濱河造成了極大的恐慌熏兄,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,817評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件树姨,死亡現(xiàn)場(chǎng)離奇詭異霍弹,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)娃弓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)岛宦,“玉大人台丛,你說(shuō)我怎么就攤上這事±危” “怎么了挽霉?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,354評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)变汪。 經(jīng)常有香客問(wèn)我侠坎,道長(zhǎng),這世上最難降的妖魔是什么裙盾? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,498評(píng)論 1 284
  • 正文 為了忘掉前任实胸,我火速辦了婚禮他嫡,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘庐完。我一直安慰自己钢属,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,600評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布门躯。 她就那樣靜靜地躺著淆党,像睡著了一般。 火紅的嫁衣襯著肌膚如雪讶凉。 梳的紋絲不亂的頭發(fā)上染乌,一...
    開(kāi)封第一講書(shū)人閱讀 49,829評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音懂讯,去河邊找鬼荷憋。 笑死,一個(gè)胖子當(dāng)著我的面吹牛域醇,可吹牛的內(nèi)容都是我干的台谊。 我是一名探鬼主播,決...
    沈念sama閱讀 38,979評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼譬挚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼锅铅!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起减宣,我...
    開(kāi)封第一講書(shū)人閱讀 37,722評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤盐须,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后漆腌,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體贼邓,經(jīng)...
    沈念sama閱讀 44,189評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,519評(píng)論 2 327
  • 正文 我和宋清朗相戀三年闷尿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了塑径。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,654評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡填具,死狀恐怖统舀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情劳景,我是刑警寧澤誉简,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站盟广,受9級(jí)特大地震影響闷串,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜筋量,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,940評(píng)論 3 313
  • 文/蒙蒙 一烹吵、第九天 我趴在偏房一處隱蔽的房頂上張望碉熄。 院中可真熱鬧,春花似錦年叮、人聲如沸具被。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,762評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)一姿。三九已至,卻和暖如春跃惫,著一層夾襖步出監(jiān)牢的瞬間叮叹,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,993評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工爆存, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蛉顽,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,382評(píng)論 2 360
  • 正文 我出身青樓先较,卻偏偏與公主長(zhǎng)得像携冤,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闲勺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,543評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容