0%

第五章 解构

解构

对象和数组字面量是js中最常用的标记,并且多亏JSON数据格式的流行它们成为了这面语言重要的一部分。对于定义对象和数组时相当常见的,同样从这些结构中系统的取出需要的部分也是同样常见。ES6为了简化这个过程添加了destructuring(解构:将大数据结构分解为很多小数据结构的过程)。这种中将为你展示如何使用在对象和数组上使用解构。

为什么解构是有用的

在ES5和之前,为了从对象和数组中提取信息需要很多看起来一样的代码,如:

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

// extract data from the object
let repeat = options.repeat,
save = options.save;

这段代码为了从对象options获取repeat和save值并将之用保存到同名的本地变量,虽然这段代码看起简单但是想象下如果你有很多变量需要赋值,你需要一个一个赋值.并且如果这是一个混乱的数据结构你需要为了一点数据而遍历整个数据结构。

对象解构

对象解构语法:用对象字面量赋值操作符左边部分表示,如:

1
2
3
4
5
6
7
8
9
let node = {
type: "Identifier",
name: "foo"
};

let {type, name} = node;

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

这段代码中node.type和node.name的值分别存在了type和name中,这个语法和第4章中介绍的对象字面量初始化速记一样。标识符type和name声明为本地变量并从node对象中读取值。

  • note:在使用解构声明变量(var let const)都必须提供一个初始化值(等号右边)

解构赋值

到目前为止对象解构都是在变量声明上,但是在赋值中也可以用解构,如下,当你在变量定义后想重新赋值时:

1
2
3
4
5
6
7
8
9
10
11
12
let node = {
type: "Identifier",
name: "foo"
},
type = "Literal",
name = 5;

// assign different values using destructuring
({ type, name } = node);

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

在上面代码中,对象node中type和name在声明的同时赋值初始化,同时两个同名变量type和name声明并赋值初始化。下一行代码中利用解构变量从node对象中读取相应的值,注意你必须在解构赋值语句两步加园括号。这是因为花括号一般期待为一个块级语句,同时块级语句是不能出现在赋值语句左边的。加上圆括号后的花括号表示不应该解释为块级语句而应该解释为表达式。

一个解构赋值表达式的求值结果为表达式右边(=号的后面),这意味着你可以在任何需要使用解构赋值表达式求值结果的地方地方使用。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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"

调用函数uptpuInfo()时传入了一个解构赋值表达式。这个表达式求值结果为node,同时type和name都被赋值。

  • warning: 如果解构赋值表达式的(=号后面的表达式)右边求值为null或者undefined则会抛出一个错误,这是因为任何尝试在null或者undefined读取属性都将抛出一个运行时错误。

默认值

当你在使用解构赋值语句时,你指定的本地变量在对象上并不存在同名属性时,本地变量会被赋值为undefined,如:

1
2
3
4
5
6
7
8
9
10
let node = {
type: "Identifier",
name: "foo"
};

let { type, name, value } = node;

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

这段单面定义了一个多余的本地变量value并且尝试赋值,但是在node上并没有对应的value属性,所以最终被赋值为undefined。

你可以选择性的为特定不存在的属性添加默认值,你只需要在指定属性名后面加上=和相应的默认值,如:

1
2
3
4
5
6
7
8
9
10
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时起效,这个和第三章中函数的默认参数相似。

赋予不同的本地变量名

目前为止,每个解构赋值都是利用的对象属性名和本地变量名相同的条件,但是ES6允许你使用不同名字来赋值,如下:

1
2
3
4
5
6
7
8
9
let node = {
type: "Identifier",
name: "foo"
};

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

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

数组解构

数组解构语法与对象解构语法相当相似,它只是用数组字面量语法代替了对象字面量语法而已,具体的解构操作由数组中的位置决定,而不是对象中属性名。如:

1
2
3
4
5
6
let colors = [ "red", "green", "blue" ];

let [ firstColor, secondColor ] = colors;

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

这里的数组解构将数组中”red”和”green”值存在变量firstColor和secondColor上,之所以会这样是因为它们在数组中的位置,至于变量名字可以随便取,如果在对应位置没有响应的变量名解构将忽略它。记住解构过程是不会给不数组本身。

在数组解构中你也可以省略掉你不想要的值只在对应位置提供相应的变量名,如下:

1
2
3
4
5
let colors = [ "red", "green", "blue" ];

let [ , , thirdColor ] = colors;

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

这段代码利用了解构赋值来获取数组(colors)里的第三项。在thridColor前面的逗号都是数组项的占位符,你可以轻松的选出数组中间位置的变量而不需要提供前面变量的名字。

  • __warning__:和对象解构一样你必须为初始化值通过var,let,const

解构赋值

你也利用解构来赋值,但是和对象解构不一样的是你需要为变量加一层大括号,如:

1
2
3
4
5
6
7
8
let colors = [ "red", "green", "blue" ],
firstColor = "black",
secondColor = "purple";

[ firstColor, secondColor ] = colors;

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

这段代码中的解构赋值和前一次的数组解构例子相似,唯一的不同是firstColor和secondColor先定义,大多数这些就是你需要知道关于数组解构的东西了,但是更多的是你会发现知道这些就已经很有用了。

数组解构赋值有一个非常独特用处,那就是交换两个变量值。值的交换在排序算数中是一种相当常规的操作,在ES5中交换两个变量值需要一个临时变量,如:

1
2
3
4
5
6
7
8
9
10
11
// Swapping variables in ECMAScript 5
let a = 1,
b = 2,
tmp;

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

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

ES5中交换a和b值中间变量tmp是必不可少的,利用解构赋值就不再需要这样的额外变量,如下:

1
2
3
4
5
6
7
8
// Swapping variables in ECMAScript 6
let a = 1,
b = 2;

[ a, b ] = [ b, a ];

console.log(a); // 2
console.log(b); // 1
  • Warning: 和对象解构相同当解构赋值表达式右边数组项有求值为null或undefined时都将抛出一个错误。

默认值

数组解构赋值运行你为数组中任何位置项提供默认值,同样的当给定的位置不存在,或者存在值为undefined时这个默认值被使用,如:

1
2
3
4
5
6
let colors = [ "red" ];

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

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

这段代码中,colors数组只有一项,所以并没有为secondColor所匹配项,但是因为secondColor有默认项,最终它的值就为默认项值。

复杂数组解构

1
2
3
4
5
6
7
8
let colors = [ "red", [ "green", "lightgreen" ], "blue" ];

// later

let [ firstColor, [ secondColor ] ] = colors;

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

不定项数组解构

第三章为函数引进了不定参数,同样数组解构也有类似的概念不定项(rest items),不定项用…加一个变量名语法表示数组中其余项,如:

1
2
3
4
5
6
7
8
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"

colors中的第一项赋值给firstColor,然后其余项赋值给一个新数组restColors.

js数组能力中有一项明显的遗漏那就是复制,在ES5中,开发者常常使用concat()方法来代替复制数组方法,如:

1
2
3
4
5
// cloning an array in ECMAScript 5
var colors = [ "red", "green", "blue" ];
var clonedColors = colors.concat();

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

concat()方法意在将两个数组连接,当调用时没有传参将返回调用者的一份复制,在ES6中,在ES6中可以用数据结构中的不定项做相同的事情,如:

1
2
3
4
5
// cloning an array in ECMAScript 6
let colors = [ "red", "green", "blue" ];
let [ ...clonedColors ] = colors;

console.log(clonedColors); //"[red,green,
  • warning: 不定项在数组解构时必须为最后一项,不定项后面不能添加逗号如果有将报错。

混合解构

对象和数组解构可以一起使用,这样你就可以从一个对象数组相互嵌套的解构中提取想要的值。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 let node = {
type: "Identifier",
name: "foo",
loc: {
start: {
line: 1,
column: 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

这段代码将node.loc.start和node.range[0]的值提取到对应的start和startIndex中了,利用这种混合解构你就可以从JSON中提取任何你想要的值。

解构参数

解构拥有很多有用的使用场景,其中之一就为函数传参时.当js函数需要接受很多参数项时,一个常见的模式创建一个option对象来指定这些参数,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// properties on options represent additional parameters
function setCookie(name, value, options) {

options = options || {};

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

// code to set the cookie
}

// third argument maps to options
setCookie("type", "js", {
secure: true,
expires: 60000
});

许多的js库都包含setCookie()这个函数,函数name和value参数必选,但是secure,path,domain,expires可选,既然这些属性顺序都没有优先级,将这些属性添加到一个对象的属性上是一个不错的方法,但是这样一来你就不能通过看函数定义知道要传入哪些参数,你必须看函数体。

解构参数提供了一个更好替代方案,这样你就可以看函数定义而知道要传哪些参数:

1
2
3
4
5
6
7
8
9
function setCookie(name, value, { secure, path, domain, expires }) {

// code to set the cookie
}

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

必传的解构参数

解构参数有一个奇怪的地方是,在使用解构参数时如果没有提供默认值和调用时并没传解构的对象将会报错。

1
2
// Error!
setCookie("type", "js");

第三个参数缺失时,其值如预想一样将被设为undifined,造成错误的原因是因为解构参数实际上就是解构声明的简写,当调用setCookie()时,js引擎实际会这样做:

1
2
3
4
5
6
function setCookie(name, value, options) {

let { secure, path, domain, expires } = options;

// code to set the cookie
}

如果解构参数是必选的,这样的行为是正常的,但是如果你的解构参数时可选时,你可以为解构参数提供一个默认值,如:

1
2
3
4
function setCookie(name, value, { secure, path, domain, expires } = {}) {

// ...
}

解构参数默认值

你可以为解构参数指定默认值,这和你解构赋值时很像。只需要在参数后面添加等号和特定的默认参数值,如:

1
2
3
4
5
6
7
8
9
10
11
function setCookie(name, value,
{
secure = false,
path = "/",
domain = "example.com",
expires = new Date(Date.now() + 360000000)
} = {}
) {

// ...
}