1

在编码过程中,我们经常定义许多对象和数组,然后有组织地从中提取相关的信息片段。ES6的解构特性,可以简化这项工作。解构是一种打破数据结构,将其拆分为更小部分的过程。

未使用解构的做法

let options = {
    repeat: true,
    save: false
};

// 从对象中提取数据
let repeat = options.repeat;
    save = options.save;

这段代码从options对象提取了repeatsave的值并将其存储为同名局部变量。如果要提取的变量更多,甚至还包含嵌套结构,靠遍历会够呛。

解构

ES6解构的实现,实际上是利用了我们早已熟悉的对象和数字字面量的语法。

对象解构

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

let { type, name } = node;

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

可见,只要在赋值操作的左边放置一个对象字面量就可以了。typename都是局部声明的变量,也是用来从options对象读取相应值的属性名称。

数组解构

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

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

使用的是数组字面量。我们从colors数组中解构出了"red"和"green"两个值,分别存储在变量firstColorsecondColor中。

数组解构模式中,也可以直接省略元素,只为感兴趣的元素提供变量:

let colors = [ "red", "green", "blue" ];
let [ , , thirdColor] = colors; // 不提供变量的,占位一下就可以

console.log(thirdColor);   // "blue"

解构赋值

对象解构赋值

我们也可以对已声明的变量使用解构赋值:

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

// 使用解构语法为变量赋值,要用小括号扩住
({ type, name } = node);

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

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"
consolo.log(name);  // "foo"

数组解构赋值

数组解构也可以用于赋值上下文:

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

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

数组解构赋值可以很方便地交换两个变量的值。
先看看ES5,我们交换两个变量的做法:

let a = 1,
    b = 2,
    tmp;
    
tmp = a;
a = b;
b = tmp;

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

使用数组解构赋值:

let a = 1,
    b = 2;

// 左侧是解构模式,右侧是为交换过程创建的临时数字字面量
[ a, b ] = [ b, a ];

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

解构赋默认值

对象解构赋默认值:

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

let { type, name, value1, value2 = true } = node;

console.log(type);    // "Identifier"
console.log(name);    // "foo"
console.log(value1);  // undefined  在解构对象上没有对应的值
console.log(value2);  // true

数组解构赋默认值:

let colors = [ "red" ];

let [ firstColor, secondColor, thirdColor = "blue"] = colors;

console.log(firstColor);   // "red"
console.log(secondColor);  // undefined
console.log(thirdColor);   // "blue"

为非同名变量赋值

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

let { type: localType, name: localName = "bar" } = node;

console.log(localType);    // "Identifier"
console.log(localName );    // "bar"

type: localType语法的含义是读取名为type的属性并将其值存储在变量localType中。

嵌套解构

对象嵌套解构:

let node = {
    type: "Identifier",
    name: "foo",
    loc: {
        start: {
            line: 1,
            colomn: 1
        },
        end: {
            line: 1,
            column: 4
        }
    }
};

let { loc: { start }} = node;

console.log(start.line);     // 1
console.log(start.column);   // 1

上面的解构示例中,冒号前的标识符代表在对象中的检索位置,其右侧为被赋值的变量名;如果冒号后面是花括号,则意味着要赋予的最终值嵌套在对象内部更深的层级中。

也可以使用与对象名不同的局部变量:

...

let { loc: { start: localStart }} = node;
console.log(localStart.line);     // 1
console.log(localStart.column);   // 1

数组嵌套解构

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

let [ firstColor, [ secondColor ] ] = colors;

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

不定元素

ES6的函数引入了不定参数,而在数组解构语法中有个相似的概念:不定元素。
在数组中,可以通过...语法将数组中剩余的元素赋值给一个指定的变量:

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 colors = [ "red", "green", "blue" ];
// concat()方法的设计初衷是连接两个数组,不传参数会返回当前数组的副本
var clonedColors = colors.concat();

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


// ES6通过不定元素来实现相同功能
let colors = [ "red", "green", "blue" ];
let [ ...clonedColors ] = colors;

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

不定元素必须为最后一个条目,在后面继续添加逗号会导致程序抛出语法错误。

对象和数组混合解构

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

let { 
    loc: { start },
    range: [ startIndex ]
} = node;

console.log(start.line);     // 1
console.log(start.column);   // 1
console.log(startIndex);     // 0

这种方法极为有效,尤其是当你从JSON配置中提取信息时,不再需要遍历整个结构了。
请记住,解构模式中loc:和range:仅代表它们在node对象中所处的位置(也就是该对象的属性)。

解构参数

当定义一个接受大量可选参数的JavaScript函数时,我们通常会创建一个可选对象:

// options的属性表示其他参数
function setCookie(name, value, options) {
    options = options || {};
    
    let secure = options.secure,
        path = options.path,
        domain = options.domain,
        expires = options.expires;
        
    // 设置cookie的代码
}

// 第三个参数映射到options中
setCookie("type", "js", {
    secure: true,
    expires: 60000
});

许多JavaScript库中都有类似的setCookie()函数,而在示例函数中,namevalue是必需参数,而securepathdomainexpires则不然,这些参数相对而言没有优先级顺序,将它们列为额外的命名参数也不合适,此时为options对象设置同名的命名属性是一个很好的选择。现在的问题是,仅查看函数的声明部分,无法辨别函数的预期参数,必须通过阅读函数体才可以确定所有参数的情况。

如果将options定义为解构参数,则可以更清晰地了解函数预期传入的参数:

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

setCookie("type", "js", {
    secure: true,
    expires: 60000
});

对于调用setCookie()函数的使用者而言,解构参数变得更清晰了。

必须传值的解构参数

如果调用函数时不提供被解构的参数会导致程序抛出错误:

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

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

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

如果解构赋值表达式右值为nullundefined,则程序会报错。因此若调用setCookie()函数时不传入第3个参数,程序会报错。

如果解构参数是必需的,大可忽略掉这个问题;但如果希望将解构参数定义为可选的,那就必须为其提供默认值来解决这个问题:

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

示例中为解构参数添加一个新的对象作为默认值,securepathdomainexpires这些变量的值全部为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中修改,改变的数据将自动同步到所有出现过的地方。


zhutianxiang
1.5k 声望327 粉丝