聊聊ES6的解构赋值(初级篇)

553 阅读7分钟

解构

ES6引入的新特性,先简单理解一下它的含义:

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

我们创建了一个手动赋值,从foo()返回的数组中的值到个别的变量,a,b,c我们这么做就需要tmp变量,当然我们也可以用对象的方式这么做:

function bar(){
    return {      
        x:4,      
        y:5,     
        z:6   
    }
}
var tmp = bar(),
    x = tmp.x,
    y = tmp.y,
    z = tmp.z;
consolr.log(x,y,z); 
// 4  5  6

属性值tmp.x被赋值给变量x,tnp.y到y和z都一样。

从一个数组中取得索引的值或从一个对象中取得属性并手动赋值可以被认为是结构化赋值。ES6为解构增加了一种专门的语法,数组解构对象解构,这种语法消灭了tmp变量,使代码更加干净。

var [ a, b, c ] = foo();

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

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

对象属性赋值模式

让我们深入前一个代码段中的 { x : x, ... } 语法。如果属性名与你想要声明的变量名一致,你实际上可以缩写这个语法:

var {  x, y, z } = bar();
console.log(x , y , z);
// 4 5  6 

很酷吧!

我们实际上省略了 x: 部分。这看起来可能不是一个很重要的细节,如果能写缩写形式,那为什么还要写出更长的形式呢?因为更长的形式上允许你将一个属性赋值给一个不同的变量名称,这个时候很有用:

var { x :bam ,y: baz , z: bap } = bar();
console.log( bam , baz ,bap )// 4 5 6
console.log( x, y, z)
//ReferenceError: x is not defined

关于这种对象解构的形式有一个微妙但超级重要的怪异之处需要理解。为了展示为什么它可能是一个需要注意的坑,让我们考虑一下普通对象字面量模式是如何被指定的:

var X  = 10 , Y = 20;
var o = { a: X , b: Y };
console.log( o.a, o.b )
// 10 20

在 { a: X , b: Y }中,我们知道a是对象属性,而X是被赋值给它的原值。它的语义模式是 目标:源,或者是 属性别名:值。我们能直观的明白这一点,因为它和 = 赋值是一样的,而它的模式就是目标 = 源

然而,当你使用对象解构赋值时,将看起来像是对象字面量的{ .. } 语法放在 = 操作符的左手边----你反转了这个目标:源的模式。

回想一下:

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

这里面对称的模式是 源:目标(或者 值:属性别名)。x:bam 意味着属性 x是源值而bam是被赋值的目标变量。换句话说,对象字面量是target <--source,而对象解构赋值是 source -->target。有另外一种考虑这种语法的方式,可能有助于缓和这种困惑。考虑如下代码:

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 }这一行中x和y代表对象属性,在{ x:AA , y:BB }这一行中x和y也代表对象属性。在这两行中,如果你在代码中擦掉 x : 和 y : 部分,仅留下aa,bb 和AA,BB,它的效果从概念上讲是不能的,将是从aa赋值到AA,从bb赋值到BB。所以这种平行性也许有助于解释为什么对于这种ES6特性,语法模式被故意的反转了。

不仅是声明

至此,我们一直将解构赋值与var 声明(当然也可以使用let 和const )一起使用,但是解构是一种一般意义上的赋值操作,不仅是一种声明。

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

变量可以是已经被定义好的,然后解构仅仅负责赋值,正如我们看到的那样。

注意:特别对于对象解构形式来说,当我们省略了var、let、const声明时,就必须将整个赋值表达式包含在()中,因为如果不这样做的话左手边作为语句的第一个元素的 { .. } 将被视为一个语句块而不是一个对象。

事实上 ,变量表达式(a , y,等等)不必是一个变量标识符。任何合法的赋值表达式都是允许的。例如:

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

你甚至可以在解构中使用计算型属性名:

var whice = 'x',o = { } ;
( { [ whice  ] : o[ whice ] } = bar() )
console.log( o.x )
//4

[ whice ]: 的部分是计算型属性名,它的结果是x,将从当前的对象中拆解出来的作为赋值的源头的属性。

o[ whice ]的部分只是一个普通的对象键引用,作为赋值的目标来说它与o.x是等价的。

也可以使用普通的赋值来创建对象映射/变形:

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

也可以将对象映射进来一个数组:

var o1 = { a:1, b:2, c:3 },
o2 = [];
( { a: o2[0], b:o2[1], c:o2[2] } = o1 );
console.log( o2 )
// [ 1, 2, 3]

或者从另一个方向:

var a1 = [ 1, 2, 3],
o2 = { };
[ o2.a, o2.b, o2.c ] = a1;
console.log( o2.a, o2.b, o2.c )
// 1 2 3 

也可以将一个数组重排到另一个数组

var a1 = [ 1, 2, 3 ],
a2 =[ ] ;
[ a2[2], a2[0], a2[1] ] = a1;
console.log( a2 )
// [  2, 3, 1 ]

还可以不使用临时变量来解决传统的交换两个变量的问题

var x = 10 ,
    y = 20;
[ y, x ] = [ x , y] ;
console.log( x,y )
// 20 10

注意:不能将赋值和声明混在一起,除非你想要所有赋值的表达式也被视为声明。否则,你会得到一个语法错误;

重复赋值

对象解构形式允许源属性(持有任意值得类型)被罗列多次。

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

这可以解构一个子对象/数组属性,也可以捕获这个子对象/数组的值本身

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

提醒:像我们现在把所有的解构赋值都罗列在一行,然而,一个好的多的注意是使用恰当的缩进将解构赋值的模式分散在多行中,和我们写的JSON或对象字面量非常相似——为了可读性。解构的目的不是为了少打字,更多的是为了声明的可读性。

解构赋值表达式

带有对象或数组解构的赋值表达式的完成值是右手边完整的对象/数组值。

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被赋值为对象o的引用,而不是a, b ,或者c的值,数组解构也是一样:

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

通过将这个对象/数组作为完成值传递下去,你可将解构赋值表达式链接在一起:

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

太多,太少,正合适?

对于数组解构赋值和对象解构赋值两者来说,你不必分配所有出现的值:

var [ , b ] = foo();
var { x, z} = bar();
console.log( b, x ,z);
// 2 4  6 

从foo()返回的值1和3被丢弃了,从bar()返回的值5也是。相似的如果你试着分配比你正在解构/拆解的值要多的值时,它们会如你所想的那样安静的退回到undefined:

var [ ,,c,d ] = foo();
var { w,z } = bar();
console.log( c ,z )
// 3  6  
console.log( d, w )
//undefined undefined

这种行为平行的遵循早先提到的“undefined意味着缺失”的原则

默认值赋值

两种形式的解构都可以为赋值提供默认值选项,它使用和早先讨论过得默认函数值相似= 语法。

var [ a= 3,b=6,c=9,d=12 ] = [1,2,3];
var { x= 5,y= 10, z= 15, w= 20 } = { x : 4, y:5, z:6 }
console.log(a,b, c,d)
// 1 2  3 12 
console.log( x,y,z,w )
// 4 5 6 20

也可以将默认值赋值与前面说过的赋值表达式语法组合到一起:

var { x , y ,z ,w: WW= 20 } = bar();
console.log( x, y , z ,WW)
// 4 5 6 20

如果你在一个解构中使用一个对象或者数组作为默认值,那么要小心不要把自己搞糊涂了。

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 );
console.log( x.y , y.y, z.y )
// 300 100 42

ES6文档caibaojian.com/es6/destruc…

希望此篇文章给大家带来帮助!本文章部分摘自**《你不懂的JS》**!技术搬运工!

下一篇:聊聊ES6的结构赋值(进阶篇)