对象和数组字面量是JavaScript中两种最常用的数据结构,由于JSON数据格式的普及,二者已经成为语言中最重要的一部分。在代码中,我们经常定义很多对象和数组,然后从去提取相关的信息片段,ES6为简化这种任务引入了新特性:解构(destructuring assignment)。比如
let a, b, rest;
[a, b] = [10, 20];
console.log(a);
// expected output: 10
console.log(b);
// expected output: 20
[a, b, ...rest] = [10, 20, 30, 40, 50];
console.log(rest);
// expected output: Array [30,40,50]
执行结果如下
The destructuring assignment syntax is a JavaScript expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
在ES5以及早起版本中,开发者为了从对象和数组中获取特定数据并赋值给变量,需要编写许多看起来同质化的代码,比如
let options = {
repeat: true,
save: false
};
// 从对象中提取数据
let repeat = options.repeat,
save = options.save;
从options对象中提取repeat和save的值然后将其存储到同名的局部变量,提取的过程极其相似,如果需要提取更多变量,则必须一次编写更多类似的代码来为变量赋值,如果其中还包含嵌套解构,只靠遍历时找不到真实信息的,必须要深入挖掘整个数据结构才能找到所需数据。所以ES6为对象和数组都添加了解构功能,将数据结构打散的过程变得更加简单,可以从打散后更小的部分获取所需信息。
对象解构的语法形式是在赋值操作符左边放置一个对象字面量,例如上面的示例修改如下
let options = {
repeat: true,
save: false
};
// 从对象中提取数据
let { repeat, save } = options;
// true
console.log(repeat);
// false
console.log(save);
在这段代码中,options.repeat的值被存储在名为repeat的变量中,options.save的值被存储在名为save的变量中。
如果使用var、let或const解构声明变量,则必须提供初始化程序(也就是等号右侧的值),否则会抛出如下异常:Uncaught SyntaxError: Missing initializer in destructuring declaration
除了可以在定义变量时使用解构语法,也可以在定义变量之后通过解构语法修改值。
let options = {
repeat: true,
save: false
}, repeat = false, save = true;
// 从对象中提取数据
({ repeat, save } = options);
// true
console.log(repeat);
// false
console.log(save);
在这里通过解构赋值的方法从options对象中提取值重新为变量赋值。但是要注意,一定要用一对小括号来包裹解构赋值语句。
JavaScript引擎将一对开放的花括号视为一个代码块。而语法规定,代码块语句不允许出现在赋值语句左侧,添加小括号之后可以将块语句转化为一个表达式,从而实现了整个解构赋值的过程。
解构赋值表达式的值与表达式右侧的值相等。如此一来,在任何可以使用值的地方都可以使用解构赋值表达式。想象一下给函数传递参数值的过程:
let options = {
repeat: true,
save: false
}, repeat = false, save = true;
function output(value) {
console.log(value === options);
}
output({ repeat, save } = options)
调用output函数时传入一个解构表达式,由于JavaScript表达式的值为右边的值,因此此处传入的参数等同于options,且变量repeat和save被重新赋值,最终将options传入了output函数。
解构赋值表达式(也就是右侧的表达式)为null或undefined会导致程序抛出错误。也就是说,任何尝试读取null或undefined的属性的行为都会触发运行时错误。
使用解构赋值表达式时,如果指定的局部变量名称在对象中不存在,那么这个局部变量会被赋值为undefined,比如
let options = {
repeat: true,
save: false
};
let { repeat, save, value } = options;
// true
console.log(repeat);
// false
console.log(save);
// undefined
console.log(value);
这段代码额外定义了一个局部变量value,然后尝试为它赋值,然而在options对象上,没有对应名称的属性值,所以像预期中的那样最终它的值为undefined。
当指定的属性不存在时,可以随意定义一个默认值,在属性名称后添加一个等号和相应的默认值即可。
let options = {
repeat: true,
save: false
};
let { repeat, save, value = true } = options;
// true
console.log(repeat);
// false
console.log(save);
// true
console.log(value);
在上面的案例中,为变量value设置的默认值true,只有当options上没有该属性或者该属性位undefined时该值才生效。此处没有options.value属性,所以value使用了预设的默认值。
如果对象中属性值为null,默认值是不会起效的。因为null在这里也是被视为有效值。
在前面的案例中,解构赋值使用的都是与对象属性同名的局部变量,但如果希望使用不同命名的局部变量来存储对象属性的值。ES6中的扩展语法可以满足需求,这个语法与完整的对象字面量属性初始化程序很像。
let options = {
repeat: true,
save: false
};
let { repeat: localRepeat, save: localSave } = options;
// true
console.log(localRepeat);
// false
console.log(localSave);
这段代码使用了解构赋值来声明变量localRepeat和localSave,这两个变量分别包含options.repeat和options.save属性的值。 repeat: localRepeat语法的含义时读取名为repeat的属性并将其值存储在变量localRepeat中。这种语法实际上与传统对象字面量的语法相悖,原来的语法名称在冒号左边,值在右边。现在变量名称在冒号右边,而需要读取的位置在左边。
当时用其他变量名赋值时也可以使用默认值,只需要在变量名后面添加等号和默认值即可。
let options = {
repeat: true,
// save: false
};
let { repeat: localRepeat, save: localSave = true } = options;
// true
console.log(localRepeat);
// true
console.log(localSave);
解构嵌套对象仍然与对象字面量的语法相似,可以将对象拆解以获取你想要的信息
let options = {
repeat: true,
// save: false
loc: {
start: {
line: 1, column: 1
},
end: {
line: 1, column: 4
}
}
};
let { loc: { start } } = options;
// 1
console.log(start.line);
// 1
console.log(start.column);
在这个示例中,我们在解构模式中使用了花括号,其含义时在找到了options对象中的loc属性后,应该深入一层继续查找start属性。在上面的解构示例中,所有冒号前的标识符都代表了对象中的检索位置,其右侧为被赋值的变量名:如果冒号后是花括号,则意味着要赋予的最终值嵌套在对象内部更深的层次中。
也可以使用一个与对象属性名不同的局部变量名,比如
let options = {
repeat: true,
// save: false
loc: {
start: {
line: 1, column: 1
},
end: {
line: 1, column: 4
}
}
};
let { loc: { start: localStart } } = options;
// 1
console.log(localStart.line);
// 1
console.log(localStart.column);
在这个版本中,options.loc.start被存储在了新的局部变量localStart中,解构模式可以应用于任意层级深度的对象,且每一层都具备同等的功能。比如
let options = {
repeat: true,
// save: false
loc: {
start: {
line: 1, column: 1
},
end: {
line: 1, column: 4
}
}
};
let { loc: { start: { line: localLine } } } = options;
// 1
console.log(localLine);