ES6变量的解构赋值

    什么叫做解构?解构就是按照一定的模式,从数组或者对象中提取到对应的元素,然后把这些元素复制给新的变量。这样的过程叫做解构赋值。其中要注意两个地方,第一是提取对象必须是数组或者对象。如果不是对象或者数组,ES6会先把它转换成对象。如果转换不了,如null,undefined,则会报错。表示不能够解构。第二解构的作用是为了我们方便赋值。这是ES6的新用法。我写这篇文章的时候,火狐是已经实现了的。但是谷歌还没有。

    Destructuring allows us to extract values from arrays and objects (even deeply nested) and store them in variables with a more convenient syntax.


    直观感受

    先看两个简单的小例子,直观的感受下,解构的好处。
    对于数组过去我们是这样的:

    1
    2
    3
    4
    let arr = [1,2];
    a = arr[0];
    b = arr[1];
    //a->1,b->2

    现在我们是这样的:

    1
    2
    let [a,b]=[1,2];
    //same result

    对于对象我们以前是这样的:

    1
    2
    3
    let obj = {username:'Seven',password:'123'};
    let username = obj.username;
    let password = obj.password;

    现在我们这样就行了:

    1
    2
    3
    let obj = {username:'Seven',password:'123'};
    let {username,password} = obj;
    console.log(username);

    有没有觉得很方便?:) ,注意这里的{}里面的起名必须和obj里面的键相同,否则得不到正确的值。


    数组的解构赋值

    总结下,当我们使用:

    1
    2
    3
    var [var1,var2,var3]=arr
    const [var1,var2,var3]=arr
    let [var1,var2,var3]=arr

    的时候,我们就是在用数组的解构赋值啦。

    刚刚讲到概念的时候提到,就算是even deeply nested的时候仍然可以是赋值的。比如说:

    1
    2
    3
    4
    5
    let arr = [2,3,[4,5],6];
    let [two,three,[four,five],six]=arr;
    console.log(four);//4
    //或者可以这样写
    let [two,three,[four,five],six]=[2,3,[4,5],6];

    所以我们实际上可以这样理解,解构赋值这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。

    下面看几个例子:

    1
    2
    3
    4
    let [,,three]=[1,,3] //three:3
    let [x, , y] = [1, 2, 3];//x:1,y:3
    let [one,...theothers]=[1,2,3,4];//one:1,theothers:[2,3,4]
    let [x, y, ...z] = ['a'];//x:a,y:undefined,z:[]

    如果这种解构不对应,没有成功,也就是不是模式匹配,则就是undefined,如下面两个例子:

    1
    2
    let [two]=[]; //two:undefined
    let [one,two]=[1]; //two:undefined

    上面是数组的解构赋值,但要注意一个问题,如果右边不是数组的话,是会报错的。比如:

    1
    2
    3
    4
    5
    let [one]=1;
    let [one]=null;
    let [one]=false;
    let [one]={};
    let [one]=undefined;

    都会报下面的错:TypeError: (intermediate value)['@@iterator'] is not a function

    如果等号的右边不是数组(或者严格地说,不是可遍历的结构,参见《Iterator》一章),那么将会报错。上面的表达式都会报错,因为等号右边的值,要么转为对象以后不具备Iterator接口(前五个表达式),要么本身就不具备Iterator接口(最后一个表达式)。事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。–from 阮一峰-解构赋值


    数组解构可以有默认值

    如下面几个例子:

    1
    2
    var [one=1]=[];//one:1
    var [one,two=2]=[1];//one:1,two:2

    意思就是如果后面有值,就是用后面的值,如果后面没有值,就看有没有默认的,有的话就用默认的。再否则的话,就是取不到值啦。
    那么什么标准是有值,没值呢?ES6规定,如果严格等于(===)undefined,那么就是没有值,用默认值。
    比如下面的情况:

    1
    2
    let [x=1]=[undefined];
    let [y=1]=[null];

    这个时候的情况就是:x=1,y=null啦,因为null不严格等于undefined。

    并且这个取值还是惰性的,也就是只要后面判断的值不是undefined就不会是理会前面的默认赋值。下面这个例子中:

    1
    2
    3
    4
    function test(){
    return '1';
    }
    let [f=test()]=[1];

    此时f的值是1,因为不为undefined,所以test()这个函数根本不会执行。这叫做惰性取值。

    再就是既然表达式可以(注意上面的例子并不是错的,只是因为后面有值,才不执行而已),那么变量也可以。但是变量一定还是要注意哪个原则,先声明后,才可以使用。所以下面的例子中:

    1
    2
    3
    4
    let [x=1,y=x]=[];//x=1,y=1
    let [x=1,y=x]=[2];//x=2,y=2
    let [x=1,y=x]=[3,4];//x=3,y=4
    let [x=y,y=1]=[1,2];//报错

    对象的解构赋值

    我们最开始的时候提到了:{}里面的起名必须和obj里面的键相同,否则得不到正确的值。
    这也是数组和对象的解构赋值的最大区别。因为数组是顺序结构,所以都是按找顺序取值就行了。但是对象不是单纯的以栈的数据结构来存储数据的,它包括了堆和栈。所以象的属性没有次序,变量必须与属性同名,才能取到正确的值。

    基本的对象的解构赋值,是长这个样子的:

    1
    2
    let {A,B}={A:a,B:b}
    let {B,A}={A:a,B:b}

    上面的结果都是A=a,B=b;A,B的顺序没有影响,只要名字对应就行喽。

    其实它内部是这样的:

    1
    let {A=A,B=B}={A:a,B:b};

    所以如果比如我们想把a取出来,但是不想放在A这个名字里,可以向这样:

    1
    let {A=C,B=D}={A:a,B:b};

    这个时候,我们打印处C,D的时候,就是值为a或者b。真正被赋值的是后面的这个C,D,如果没有C,D的时候赋值的才是A,B。

    还要注意如果是用let或者const声明的话,这里的A,B,C,D都不能被赋值过,否则会报错。因为let和const中变量不能重新声明。如果是var还是可以的。

    1
    2
    let C='test';
    let {A=C,B=D}={A:a,B:b};//error

    如果把第二个let去掉也是可以的。这要注意。

    刚刚我们讲过数组嵌套时,只要对应就行。对象也是可以嵌套的。如下面的例子:

    1
    2
    3
    var obj = {get:['first',{inner:'second'}]};
    var {get:[x,{inner}]};
    console.log(x+" "+y);//first second

    注意这里的get不会被赋值,它在此处代表模式。如果你尝试打印出get,会得到ReferenceError: p is not defined的错。

    与数组的解构赋值一样,对象的解构赋值也可以使用默认参数。


    对象解构也可以有默认值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    var {one=1} = {}; //one:1
    var {one,two=2}={1}; //one:1,two:2
    var obj={username:'Seven',password:'123'};
    var {username='匿名',password='null'}=obj; //username:Seven,password:123
    var {username:name='游客',password='null'} = {};//name:游客,password:null
    var {username}={wrong:'游客'};//username=undefined,解构失败,为undefined

    上面这都是声明的时候直接赋值了。如果我们把声明和赋值分开,如下面的情况:

    1
    2
    3
    var username;
    {username}={username:'Seven'} //wrong
    ({username}={username:'Seven'}) //true

    会报语法错误,SyntaxError: expected expression, got '='这是由于{}在开头会被解析为代码块。解决方法是我们用()把它扩起来。如上面所示。

    那么这个对象的解构赋值用途大吗?其实可好用了,比如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var obj = {
    username:'Seven',
    password:'123',
    highestScore:function(){
    console.log(100);
    },
    lowestScore:function(){
    console.log(60);
    }
    }
    var {username,password,highestScore,lowestScore}=obj;

    这样我们分别把普通值和方法赋值给了相应的变量。当我们打印处username的时候是Seven,当我们console.log(highestScore());的时候会得到100啦。


    更多用途

    上面我们已经提到了一种最简单的用途,其实还有很多其他的用途,比如:

    可以在函数中用
    1
    2
    3
    4
    function fn([x,y]){
    return x*y;
    }
    console.log(fn([1,2]));
    可以在循环中用
    1
    2
    [[1,2,3],[4,5,6]].map(([a,b,c])=>a+b+c;) //[6,15]
    [1,undefined,2].map((x='set')=>x) //1,'set',2

    上面的这几个例子,可能你会说,如果我不用解构,用原始传参也可以啊,看了下面的几个,你就不会这么说了。:)

    返回多个值

    以前如果我们要返回多个值,就要把值包装成对象 或者数组,然后函数外面时候在拆分,这多麻烦。有了解构赋值,我们可以很容易就实现这一点。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function test(){
    return [1,5,'Seven'];
    }
    var [a,b,c]=test();
    function test1(){
    return {first:'1',second:'2',str:'this is a string'};
    }
    var {first,second,str}=test1();

    这样我们就很容易的把每个值,直接放到变量里了。但是还是要注意,使用对象的解构时,要名称对应。

    将参数和值对应起来

    以前我们在传参的时候,要时刻注意参数的对应。如果不对应,函数变量对应的值,就是不对的。

    1
    2
    function test(a,b,c){}
    test(1,2,3);

    用数组的话是对应的。也是一组有次序的值。这个时候跟我们以前的用法没有太大的区别。

    1
    2
    function test([a,b,c]){}
    test([1,2,3]);

    但是当是对象的时候,就不是这样了。对象可以是一组无序的参数。

    1
    2
    function test({x,y,z}){}
    test({y:1,x:2,z:4})

    快速提取json对象中的值
    1
    2
    3
    4
    5
    6
    var jsonobj = {
    username:'seven',
    password:'124',
    hobbies:['travelling','singing']
    }
    var {username,password,hobbies} = jsonobj;

    这个跟我们先前讲的highestScore(),有点类似。

    默认参数

    过去我们可能是这样的。

    1
    2
    3
    4
    wechat = function(url,username,message){
    username = username || 'Seven';
    message = message || 'message is null';
    }

    现在我们这样就行了:

    1
    2
    3
    4
    5
    6
    wechat = function(url,{
    username='Seven',
    message='message is null',
    isLogin = 'no',
    cache = 'no'
    }){}

    通过这样的传参形式,就避免了类似a=a||'test'这种写法。


    总结

    主要是把变量的解构赋值,这块给理解了下。ES6的新特性要在平时刻意的多用。等到以后完全被浏览器支持的时候,用起来就方便多了。解构赋值的用途还是很多的,我觉得很棒。