美文网首页JavaScriptWeb前端之路程序员
Destructuring assignment(解构赋值)in

Destructuring assignment(解构赋值)in

作者: 2f1b6dfcc208 | 来源:发表于2017-08-03 15:45 被阅读68次

    ES6允许按照一定模式,从数组和对象(以及字符串、数值、布尔值、函数参数等)中提取值,按照对应位置给变量进行赋值,这被称为解构赋值。首先,假定你已经了解了什么是解构赋值,我们先来快速看一下它的常见用途。
    1. 交换变量的值

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

    与传统的引入中间变量进行值交换的方式相比,这种写法不仅简洁,而且易读,语义非常清晰
    2. 解析函数多返回值
    JS中函数只能返回一个值,如果要返回多个值,只能将它们放在数组或对象里返回,通过解构赋值,我们可以很方便的将这些值取出来并赋给其它变量。

    //返回一个数组
    function arr(){
        return [1,2,3];
    }
    let [x,y,z]=arr();
    //返回一个对象
    function obj(){
        return {
            foo:1,
            bar:2
        }
    }
    let {foo,bar}=obj();
    

    3. 定义函数多参数传入方式
    解构赋值可以很方便地将一组实参与形参变量名对应起来。

    //参数是一组有序的值
    function f([x,y,z]){...}
    f([1,2,3]);
    //参数是一组无序的值
    function f({x,y,z}){...}
    f({y:2,x:1,z:3});
    

    4. 提取JSON数据
    解构赋值对提取JSON对象中数据,尤其有用。

    let jsonData={
        id:20,
        status:'OK',
        data:[12,34]
    };
    let {id,status,data:number}=jsonData;
    console.log(id,status,number);//20 'OK' [12,34]
    

    5. 遍历部署了Iterator接口的数据结构时,即在for ... of循环遍历自身每一项时,可以直接多参数传值。Map结构原生支持Iterator接口,使用变量的解构赋值,可以很方便的获取键名和键值。

    var map=new Map();
    map.set('first','hello');
    map.set('second','world');
    for(let item of map){
        console.log(item);
    }
    for(let [key,value] of map){
        console.log(key+'is'+value);
    }
    for(let [i,j] of map){
        console.log(i+' is '+j);//结果同上,这里可以将每项item看做数组,所以可以用自定义变量获取对应位置的值
    }
    

    如果只想获取键名,或者只想获取键值,可以写成下面这样

    //获取键名
    for (let [key] of map){...}
    //获取键值
    for (let [,value] of map){...}
    

    6.获取模块的指定方法
    加载模块后,往往需要再次指定变量获取模块的某些方法。解构赋值直接精简了这一过程。

    const {SourceMap,SourceNode} = require('source-map');
    

    接下来详细介绍Destructuring assignment.

    1. 数组的解构赋值

    以前,为变量赋值,只能直接指定值

    let a=1,b=2,c=3;
    

    现在ES6允许这样

    let [a,b,c]=[1,2,3]
    

    看起来和golang的多参数赋值差不多,这里的代码实际上表示从数组中提取值,按照对应位置赋给变量。本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。

    let [foo,[[bar],bar]]=[1,[[2],3]];
    let [,,third]=['foo','bar','baz']
    let [x,,y]=[1,2,3]
    let [head,...tail]=[1,2,3,4];
    let [x,y,...z]=['a'];
    

    如果解构不成功,变量的值将等于undefined。若等号右边的值不是可遍历的数据结构(没有Iterator接口),那么将会报错

    //报错
    let [foo]=1;
    let [foo]=undefined;
    let [foo]=null;
    let [foo]=false;
    let [foo]=NaN;
    let [foo]={};//注意,普通对象本身不具备Iterator接口,不可遍历
    //for循环遍历,数组和对象都可使用for...in遍历,其中数组遍历时传入的参数值是索引,对象传入的是属性,数组还可以使用forEach方法进行遍历
    

    对于Set结构,也可以使用数组的解构赋值

    let [x,y,z]=new Set(['a','b','c']);
    

    重点:事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。

    function* fibs(){
        let a=0,b=1;
        while(true){
            yield a;
            [a,b]=[b,a+b];
        }
    }
    let [first,second,third,fourth,fifth,sixth]=fibs();
    console.log(sixth);//5
    

    上面代码中,fibs是一个Generator函数,原生具有Iterator接口,解构赋值会依次从这个接口获取值。
    解构赋值允许指定默认值,只有当数组成员严格等于undefined时,默认值才会生效。如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。

    function f(){...}
    let [x=f()]=[1];
    

    上面的代码中,因为x能取到值,所以函数f根本不会执行。

    2. 对象的解构赋值

    解构赋值不仅可以用于数组,还可以用于对象

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

    比较常见的是在Node导出模块对象时:

    let a=1,b=2;
    //let obj={a,b}
    module.exports={a,b}
    

    对象的解构与数组有一个重要的不同,数组的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名才能取到正确的值。

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

    上面的第一行语句,等号左边的两个变量的次序与等号右边两个同名属性的次序不一致,但是对取值完全没有影响,第二行语句中左边的变量在等号右边没有对应的同名属性名,所以取不到值,为undefined
    若所需要的变量名明确与属性名不一致,则必须写成下面这样:

    let {foo:baz}={foo:'aaa'};
    console.log(baz);//aaa
    
    let {first:f,last:l}={first:'hello',last:'world'}
    console.log(f,l);//hello world
    

    这实际上说明,对象的解构赋值是下面形式的简写:

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

    也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再将该属性的值赋给模式匹配的变量,真正被赋值的是后者,而不是前者。对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。
    由于数组本质是一种特殊的对象,因此可以对数组进行对象属性的解构

    let arr=[1,2,3];
    let {0:first,[arr.length-1]:last}=arr;//方括号这种写法,属于“属性名表达式”
    console.log(first,last);//1 3
    

    3. 字符串的解构赋值

    字符串也可以解构赋值,这是因为字符串此时会先被默认转化为一个类似数组的对象

    const [a,b,c,d,e]='hello';
    console.log(a,b,c,d,e);
    let {length:len}='hello';
    console.log(len);// 5 类似数组的对象都有一个length属性
    

    4. 数值和布尔值的解构赋值

    解构赋值时,如果等号右边是数值和布尔值,则会先转化为对象。

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

    解构赋值的规则是,如果等号右边的规则不是对象或者数组时,就先将其转化为对象。若无法转化为对象,如nullundefined,则会报错

    5. 函数参数的解构赋值

    函数的参数也可以使用解构赋值

    function add([x,y]){...}
    add([1,2]);
    

    上面代码中,函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x和y,对于函数内部的代码来说,它们能感受到的参数就是x和y.
    下面是另一个例子:

    [[1,2],[3,4]].map(([x,y])=>x+y);//[3,7]
    

    函数参数的解构赋值也可以使用默认值

    function move({x=0,y=0}={}){
        return [x,y]
    }
    move();//[0,0]
    move({});//[0,0]
    move({x:2});//[2,0]
    move({x:2,y:3});//[2,3]
    //只有当传入参数值为`undefined`时,才会考虑默认值,在阅读的时候可以先抛开默认值不看便于阅读
    

    上面代码中,函数move的参数是一个对象,同时设置默认值为{},通过对传入的对象进行解构,得到变量xy的值,如果解构失败,xy等于默认值。
    注意,下面的写法略微有点不同

    function move({x,y}={x:0,y:0}){
         return [x,y]
    }
    move();//[0,0]
    move({});//[undefined,undefined]
    move({x:2});//[2,undefined]
    move({x:2,y:3});//[2,3]
    

    这里函数参数的默认值为{x:0,y:0},与上面不同。

    6. 圆括号问题

    在变量的声明语句,函数参数以及赋值语句的模式之中,都不能使用圆括号,只有一种情况下可以使用圆括号:赋值语句的非模式部分。

    注意:

    (1). 当解构赋值与扩展运算符一起使用时,若扩展运算符所在的位置不是最后一个参数,程序会报错。如下:

    let { ...x, y, z } = obj; // 句法错误
    let { x, ...y, ...z } = obj;//句法错误
    

    反之,只使用了扩展运算符时不会出错

    let obj = { x,...y,...z};
    

    (2). 解构赋值的拷贝是浅拷贝,即如果一个键的值是复合类型,那么解构赋值拷贝的是这个值的引用,而不是这个值的副本。

    相关文章

      网友评论

        本文标题:Destructuring assignment(解构赋值)in

        本文链接:https://www.haomeiwen.com/subject/knuelxtx.html