为何使用解构功能

在ECMAScript 5及早期版本中,开发者们为了从对象和数组中获取特定数据并赋值给变量,编写了许多看起来同质化的代码,就像这样:

  1. let options = {
  2. repeat: true,
  3. save: false
  4. };
  5. let repeat = options.repeat,
  6. save = options.save;

如果options有几千条属性要赋值给变量 则要写几千条options.变量名 十分不便
这段代码从options对象中提取了repeat和save的值并将其存储为同名局部变量,提取的过程极为相似,想象一下,如果你要提取更多变量,则必须依次编写类似的代码来为变量赋值,如果其中还包含嵌套结构,只靠遍历是找不到真实信息的,必须要深入挖掘整个数据结构才能找到所需数据。所以ECMAScript 6为对象和数组都添加了解构功能,将数据结构打散的过程变得更加简单,可以从打散后更小的部分中获取所需信息。许多语言都通过极少量的语法实现了解构功能,以简化获取信息的过程;而ECMAScript 6中的实现实际上利用了你早已熟悉的语法:对象和数组字面量的语法。

对象解构

对象解构的语法形式是再一个赋值操作符左边放置一个对象字面量
左边放一个对象字面量 右边放对应要赋值的对象名 左边对象字面量中变量名 对应右边对象中的属性名

    let node = {
        type: "Identifier",
        name: "foo"
    };
    let { type, name } = node;
    console.log(type); //"Identifier"
    console.log(name); //"foo"

node.type的值被存储在名为type的变量中;node.name的值被存储在名为name的变量中

不要忘记初始化程序
**
如果使用var、let或const解构声明变量,则必须要提供初始化程序(也就是等号右侧的值)。下面这几行代码全部会导致程序抛出语法错误,它们都缺少了初始化程序:

    var { type, name }; //报错

    let { type, name }; //报错

    const { type, name };// 报错

解构赋值

在给变量赋值是使用解构语法。你可能在定义变量之后想要修改它们的值,例如在定义变量之后想要修改它们的值。(二次赋值)

    let node = {
        type: "Identifier",
        name: "foo"
    },
        type = "Literal",
        name = 5;

    // 使用解构鱍为多个变量赋值

    ({ type, name } = node);
    console.log(type); //"Identifier"
    console.log(name);  //"foo"

在这个示例中,声明变量type和name时初始化了一个值,在后面几行中,通过解构赋值的方法,从node对象读取相应的值重新为这两个变量赋值。请注意,一定要用一对小括号包裹解构赋值语句,JavaScript引擎将一对开放的花括号视为一个代码块,而语法规定,代码块语句不允许出现在赋值语句左侧,添加小括号后可以将块语句转化为一个表达式,从而实现整个解构赋值的过程。
解构赋值表达式的值与表达式右侧(也就是=右侧)的值相等,如此一来,在任何可以使用值的地方你都可以使用解构赋值表达式。想象一下给函数传递参数值的过程

    let node = {
        type: "Identifier",
        name: "foo"
    },
        type = "Literal",
        name = 5;
    function outputInfo(value) {
        console.log(value === node); //true
    }
    outputInfo({ type, name } = node);

    console.log(type); //"Identifier"
    console.log(name);  //"foo"

调用outputInfo()函数时传入了一个解构表达式,由于JavaScript表达式的值为右侧的值,因而此处传入的参数等同于node,且变量type和name被重新赋值,最终将node传入outputInfo()函数。

注 解构赋值表达式(也就是=右侧的表达式)如果为null或undefined会导致程序抛出错误。也就是说,任何尝试读取null或undefined的属性的行为都会触发运行时错误。

默认值

使用解构赋值值表达式时,如果指定的局部变量名称在对象中不存在,这个局部变量会被赋值为undefined

    let node = {
        type: "Identifier",
        name: "foo"
    };
    let { type, name, value } = node;
    console.log(type); //"Identifier"
    console.log(name); //"foo"
    console.log(value);//undefined

当指定的属性不存在时,可以随意定义一个默认值,在属性名称后添加一个等号(=)和相应的默认值即可

    let node = {
        type: "Identifier",
        name: "foo"
    };
    let { type, name, value = true } = node;
    console.log(type); //"Identifier"
    console.log(name); //"foo"
    console.log(value);//true

为变量value设置了默认值true,只有当node上没有该属性或者该属性值为undefined时该值才生效 此处没有node.value属性,所以value使用了预设的默认值

为非同名局部变量

使用不同命名的局部变量来存储对象属性的值

    let node = {
        type: "Identifier",
        name: "foo"
    };
          //原名: 重命名
    let { type: localType, name: localName } = node;
    console.log(localType);// Identifier
    console.log(localName);// foo

这段代码使用了解构赋值来声明变量localType和localName,这两个变量分别包含node.type和node.name属性的值。type: localType语法的含义是读取名为type的属性并将其值存储在变量localType中,这种语法实际上与传统对象字面量的语法相悖,原来的语法名称在冒号左边,值在右边;现在值在冒号右边,而对象的属性名在左边。

当使用其他变量名进行赋值时,也可以添加默认值,只需要在变量名后添加等号和默认值

    let node = {
        type: "Identifier",
    };
    //对象值: 变量名
    let { type: localType, name: localName = "bar" } = node;
    console.log(localType);// Identifier
    console.log(localName);// bar

由于node.name属性不存在,变量被默认赋值为”bar”。

嵌套对象解构

    let node = {
        type: "Identifier",
        name: "foo",
        loc: {
            start: {
                line: 1,
                column: 1
            },
            end: {
                line: 1,
                column: 4
            }
        }
    };
                            // 提取node.loc.start
    let { loc: { start } } = node;
    console.log(start.line);
    console.log(start.column);

在这个示例中,我们在解构模式中使用了花括号,其含义为在找到node对象中的loc属性后,应当深入一层继续查找start属性。在上面的解构示例中,所有冒号前的标识符都代表在对象中的检索位置,其右侧为被赋值的变量名;如果冒号后是花括号,则意味着要赋予的最终值嵌套在对象内部更深的层级中。

    let node = {
        type: "Identifier",
        name: "foo",
        loc: {
            start: {
                line: 1,
                column: 1
            },
            end: {
                line: 1,
                column: 4
            }
        }
    };
                    //提取node.loc.start 给 localStart
    let { loc: { start: localStart } } = node;
    console.log(localStart.line); //1
    console.log(localStart.column); //1

数组解构

    let colors = ["red", "green", "blue"];
    let [firstColor, secondColor] = colors;

    console.log(firstColor); //"red"
    console.log(secondColor); // "green"

指定数组中需取的值

    let colors = ["red", "green", "blue"];
    let [, , thirdColor] = colors;

    console.log(thirdColor); //blue

注 当通过var、let或const声明数组解构的绑定时,必须要提供一个初始化程序,这一条规定与对象解构的规定类似。

解构赋值

    let colors = ["red", "green", "blue"];
                firstColor = "black",
        secondColor = "purple;

       [firstColor, secondColor] = colors;

    console.log(firstColor); // "red"
    console.log(secondColor); // "green"

交换两个变量的值

es5**

    let a = 1,
        b = 2,
        tmp;

    tmp = a;
    a = b;
    b = tmp;

    console.log(a); //2
    console.log(b); //1

es6

    let a = 1,
        b = 2;
    [a, b] = [b, a];

    console.log(a)//2;
    console.log(b);//1;

默认值

也可以在数组解构赋值表达式中为数组中的任意位置添加默认值,当指定位置的属性不存在或其值为undefined时使用默认值:

    let colors = ["red"];

    let [firstColor, secondColor = "green"] = colors;

    console.log(firstColor); // red
    console.log(secondColor); // green

嵌套数组解构

嵌套数组解构与嵌套对象解构的语法类似,在原有的数组模式中插入另一个数组模式,即可将解构过程深入到下一个层级:

    let colors = ["red", ["green", "lightgreen"], "blue"];

    let [firstColor, secondColor] = colors;

    console.log(firstColor); // "red"
    console.log(secondColor); //  ["green", "lightgreen"]

     [firstColor, [secondColor]] = colors;

    console.log(firstColor); // "red"
    console.log(secondColor); // "green"

不定元素

在数组解构语法中有一个相似的概念:不定元素。在数组中,可以通过…语法将数组中的其余元素赋值给一个特定的变量

    let colors = ["red", "green", "blue"];

    let [firstColor, ...restColors] = colors;

    console.log(firstColor); // red 
    console.log(restColors.length); // 2
    console.log(restColors[0]); // green
    console.log(restColors[1]); // blue

数组复制
(复制数组的更改不影响源数组)

es5**

    var color = ["red", "green", "blue"];
    var clonedColors = colors.concat();

    console.log(clonedColors); //["red", "green", "blue"]

es6

    var color = ["red", "green", "blue"];
    var [...clonedColors] = colors;

    console.log(clonedColors); //["red", "green", "blue"]

混合解构

    let node = {
        type: "Identifier",
        name: "foo",
        loc: {
            start: {
                line: 1,
                column: 1
            },
            end: {
                line: 1,
                column: 4
            }
        },
        range: [0, 3]
    };

    // 提取node.loc.start    node.loc.range
    let { loc: { start }, range: [startIndex, startIndex2] } = node;
    console.log(start.line); //1
    console.log(start.column); //1
    console.log(startIndex); //0 
    console.log(startIndex2); //3

解构参数

解构可以用在函数参数的传递过程中,这种使用方式更特别。当定义一个接受大量可选参数的JavaScript函数时,我们通常会创建一个可选对象,将额外的参数定义为这个对象的属性:

    function setCookie(name, value, options) {
        options = options || {};

        let secure = options.secure,
            path = options.path,
            domain = options.domain,
            expires = options.expires;

                // 设置cookie的代码;
    }
    setCookie("type", "js", {
        secure: true,
        export: 6000
    })

现在的问题是,仅查看函数的声明部分,无法辨识函数的预期参数,必须通过阅读函数体才可以确定所有参数的情况

重写

    function setCookie(name, value, { secure, path, domain, expires }) {
        // 设置cookie的代码
    }
    setCookie("type", "js", {
        secure: true,
        export: 6000
    })

**

必须传值的解构参数

解构参数有一个奇怪的地方,默认情况下,如果调用函数时不提供被解构的参数会导致程序抛出错误。举个例子,调用上一个示例中的setCookit()函数,如果不传递第3个参数,会报错:

// 程序报错!
setCookie("type","js");

缺失的第3个参数,其值为undefined,而解构参数只是将解构声明应用在函数参数的一个简写方法,其会导致程序抛出错误。当调用setCookie()函数时,JavaScript引擎实际上做了这些事情

    function setCookie(name, value, options) {
        let { secure, path, domain, expires } = options;

        // 设置cookie的代码
    }

如果解构赋值表达式的右值为null或undefined,则程序会报错,同理,若调用setCookie()函数时不传入第3个参数,等同于传undefined

解决方案:提供默认值

    function setCookie(name, value, { secure, path, domain, expires } = {}) {
        // 设置cookie的代码
    }

这个示例中为解构参数添加了一个新对象作为默认值,secure、path、domain及expires这些变量的值全部为undefined,这样即使在调用setCookie()时未传递第3个参数,程序也不会报错
**

解构参数的默认值

可以为解构参数指定默认值,就像在解构赋值语句中做的那样,只需在参数后添加等号并且指定一个默认值即可:

    function setCookie(name, value,
        {
            secure = false,
            path = "/",
            domain = "example.com",
            expires = new Date(Date.now() + 360000000)
        }
    ) {

    }

在这段代码中,解构参数的每一个属性都有默认值,从而无须再逐一检查每一个属性是否都有默认值。然而,这种方法也有很多缺点:首先,函数声明变得比以前复杂了;其次,如果解构参数是可选的,那么仍然要给它添加一个空对象作为参数,否则像setCookie(”type”,”js”)这样的调用会导致程序抛出错误。这里建议对于对象类型的解构参数,为其赋予相同解构的默认参数:

    function setCookie(name, value,
        {
            secure = false,
            path = "/",
            domain = "example.com",
            expires = new Date(Date.now() + 360000000)
        } = {
                secure = false,
                path = "/",
                domain = "example.com",
                expires = new Date(Date.now() + 360000000)
            }
    ) {

    }

现在函数变得更加完整了,第一个对象字面量是解构参数,第二个为默认值。但是这会造成非常多的代码冗余,你可以将默认值提取到一个独立对象中,并且使用该对象作为解构和默认参数的一部分,从而消除这些冗余:
**

    const setCookieDefaults = {
        secure = false,
        path = "/",
        domain = "example.com",
        expires = new Date(Date.now() + 360000000)
    }

    function setCookie(name, value,
        {
            secure = setCookieDefaults.secure,
            path = setCookieDefaults.path,
            domain = setCookieDefaults.domain,
            expires = setCookieDefaults.expires
        } = setCookieDefaults
    ) {

    }

在这段代码中,默认值已经被放到setCookieDefaults对象中,除了作为默认参数值外,在解构参数中可以直接使用这个对象来为每一个绑定设置默认参数。使用解构参数后,不得不面对处理默认参数的复杂逻辑,但它也有好的一面,如果要改变默认值,可以立即在setCookieDefaults中修改,改变的数据将自动同步到所有出现过的地方。






**