什么是解构赋值?
解构赋值允许你使用类似于数组或对象字面量的语法将数组或对象的属性赋值给变量。这种语法可以非常简洁,但仍然比传统的属性访问更清晰。
在没有解构赋值的情况下,你可以像这样访问数组中的前三项:
var first = someArray[0];
var second = someArray[1];
var third = someArray[2];
使用解构赋值,等价的代码变得更加简洁和可读:
var [first, second, third] = someArray;
解构数组和可迭代对象
我们已经看到了一个数组的析构赋值的例子。语法的一般形式是:
[ variable1, variable2, ..., variableN ] = array;
这只会将数组中相应的项赋值给variable1到variableleN。如果你想同时声明你的变量,可以在赋值前添加var
、let
或const
:
var [ variable1, variable2, ..., variableN ] = array;
let [ variable1, variable2, ..., variableN ] = array;
const [ variable1, variable2, ..., variableN ] = array;
事实上,变量是一种用词不当的说法,因为你可以随心所欲地嵌套模式:
var [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo);
// 1
console.log(bar);
// 2
console.log(baz);
// 3
此外,可以跳过正在被解构的数组中的项:
var [,,third] = ["foo", "bar", "baz"];
console.log(third);
// "baz"
可以用"rest"
模式捕获数组中的所有末尾项:
var [head, ...tail] = [1, 2, 3, 4];
console.log(tail);
// [2, 3, 4]
当你访问数组中越界或不存在的项时,你会得到与通过索引获取相同的结果:undefined
:
console.log([][0]);
// undefined
var [missing] = [];
console.log(missing);
// undefined
注意,使用数组赋值模式进行解构赋值也适用于任何可迭代对象:
function* fibs() {
var a = 0;
var b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
var [first, second, third, fourth, fifth, sixth] = fibs();
console.log(sixth);
// 5
解构对象Object
对Object进行解构可以将变量绑定到对象的不同属性。可以指定要绑定的属性,然后是要绑定其值的变量。
var robotA = { name: "Bender" };
var robotB = { name: "Flexo" };
var { name: nameA } = robotA;//name是要绑定的属性,nameA是要绑定name属性值的变量
var { name: nameB } = robotB;
console.log(nameA);
// "Bender"
console.log(nameB);
// "Flexo"
当属性名和变量名相同时,有一个有用的语法快捷方式:
var { foo, bar } = { foo: "lorem", bar: "ipsum" };
console.log(foo);
// "lorem"
console.log(bar);
// "ipsum"
就像数组的解构一样,你可以嵌套和组合进一步的解构:
var complicatedObj = {
arrayProp: [
"Zapp",
{ second: "Brannigan" }
]
};
var { arrayProp: [first, { second }] } = complicatedObj;
console.log(first);
// "Zapp"
console.log(second);
// "Brannigan"
当你对未定义的属性进行解构时,你会得到undefined
:
var { missing } = {};
console.log(missing);
// undefined
你应该注意的一个潜在问题是,在没有声明变量前(当没有let、const或var时),在对象上使用解构来赋值变量会出错:
{ blowUp } = { blowUp: 10 };
// Syntax error
这是因为JavaScript语法告诉引擎解析任何以{
作为块语句开始的语句(例如,{console}
是一个有效的块语句)。解决方案是要么用圆括号括住整个表达式:
({ safe } = {});
// No errors
解构非对象、非数组或非可迭代对象的值
当你尝试在null或undefined上使用解构时,会得到一个类型错误:
var {blowUp} = null;
// TypeError: null has no properties
但是,你可以对布尔值、数字和字符串等其他基本类型进行解构,并得到undefined
:
var {wtf} = NaN;
console.log(wtf);
// undefined
这可能是出乎意料的,但进一步检查,原因是简单的。当使用对象赋值模式时,需要将被解构的值强制转换为object。大多数类型都可以转换为对象,但是null和undefined不能被转换。当使用数组赋值模式时,值必须具有迭代器(iterator)。
默认值
当你要解构的属性没有定义时,你也可以提供默认值:
var [missing = true] = [];
console.log(missing);
// true
var { message: msg = "Something went wrong" } = {};
console.log(msg);
// "Something went wrong"
var { x = 3 } = {};
console.log(x);
// 3var [missing = true] = [];
console.log(missing);
// true
var { message: msg = "Something went wrong" } = {};
console.log(msg);
// "Something went wrong"
var { x = 3 } = {};
console.log(x);
// 3
解构的实际应用
函数参数定义
作为开发人员,我们通常可以通过接受一个带有多个属性的单一对象作为参数,而不是强迫API消费者记住许多单独参数的顺序,从而暴露出更多符合人体工程学的API。当我们想要引用它的一个属性时,可以使用解构来避免重复使用这个单一形参对象:
function removeBreakpoint({ url, line, column }) {
// ...
}
配置对象参数
在前面的例子的基础上,我们还可以为要解构的对象的属性提供默认值。当我们有一个要提供配置的对象,并且该对象的许多属性已经有了合理的默认值时,这是特别有用的。例如,jQuery的ajax函数将一个配置对象作为它的第二个参数,可以这样重写:
jQuery.ajax = function (url, {
async = true,
beforeSend = noop,
cache = true,
complete = noop,
crossDomain = false,
global = true,
// ... more config
}) {
// ... do stuff
};
这避免了重复用var foo = config.foo || theDefaultFoo;
这样的写法配置对象的每个属性。
使用ES6迭代协议
ECMAScript 6还定义了一个迭代协议。当你迭代map (ES6标准库)时,你会得到一系列的[key, value]对。我们可以分解这个对象,以便更容易地访问键和值:
var map = new Map();
map.set(window, "the global");
map.set(document, "the document");
for (var [key, value] of map) {
console.log(key + " is " + value);
}
// "[object Window] is the global"
// "[object HTMLDocument] is the document"
只迭代键:
for (var [key] of map) {
// ...
}
只迭代值:
for (var [,value] of map) {
// ...
}
函数一次返回多个值
虽然多重返回值并没有被写入语言本身,但它们也不需要,因为你可以返回一个数组并分解结果:
function returnMultipleValues() {
return [1, 2];
}
var [foo, bar] = returnMultipleValues();
或者,你可以使用一个对象作为容器并命名返回值:
function returnMultipleValues() {
return {
foo: 1,
bar: 2
};
}
var { foo, bar } = returnMultipleValues();
这两种模式最终都比保留临时变量好得多:
function returnMultipleValues() {
return {
foo: 1,
bar: 2
};
}
var temp = returnMultipleValues();//临时变量temp被保留,并被引用多次
var foo = temp.foo;
var bar = temp.bar;
使用延续传递样式:
function returnMultipleValues(k) {
k(1, 2);
}
returnMultipleValues((foo, bar) => ...);
从模块导入名称
通过解构,你可以明确指定模块的哪些部分你想要使用,避免弄乱你的命名空间:
const { SourceMapConsumer, SourceNode } = require("source-map");