介绍
数组解构的元素是按次序排列的,变量的取值由它的位置决定;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
解构赋值的规则是,只要等号右边的值不是对象,就先将其转为对象,由于 undefined 和 null 无法转为对象,所以对它们
进行解构赋值,都会报错。
解构赋值对提取JSON对象中的数据,函数参数的默认值,尤其有用。
基础用法
数组与迭代器的解构
数组解构赋值的一个简单示例,其语法的一般形式为:
[ variable1, variable2, ..., variableN ] = array;
这将为variable1到variableN的变量赋予数组中相应元素项的值。如果你想在赋值的同时声明变量,可在赋值语句前加入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"
而且你还可以通过“不定参数”模式捕获数组中的所有尾随元素:
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
对象的解构
通过解构对象,你可以把它的每个属性与不同的变量绑定,首先指定被绑定的属性,然后紧跟一个要解构的变量。
var robotA = { name: "Bender" };
var robotB = { name: "Flexo" };
var { name: nameA } = robotA;
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(null没有属性)
然而,你可以解构其它原始类型,例如:布尔值、数值、字符串,但是你将得到undefined:
var {wtf} = NaN;
console.log(wtf);
// undefined
你可能对此感到意外,但经过进一步审查你就会发现,原因其实非常简单。当使用对象赋值模式时,被解构的值需要被强制转换为对象。大多数类型都可以被转换为对象,但null和undefined却无法进行转换。当使用数组赋值模式时,被解构的值一定要包含一个迭代器。
默认值
当你要解构的属性未定义时你可以提供一个默认值:
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);
// 3
技巧用法
1. 交换变量
let a = 1;
let b = 2;
[a, b] = [b, a];
a; // => 2
b; // => 1
这种方式并没有什么限制。你还可以同时交互更多的变量值,比如:
let zero = 2;
let one = 1;
let two = 0;
[zero, one, two] = [two, one, zero];
zero; //=> 0
one; //=> 1
two; //=> 2
2. 访问数组,有一个数组,这个数组有可能是空的。有一种需求是访问任意位置数组元素,如果这个位置为空,则返回一个默认值。
const colors = [];
const [firstColor = "white"] = colors;
firstColor; //=> "white"
第二个
const colors = [];
const [, secondColor = "black"] = colors;
secondColor; //=> "black"
3. 不可变操作
将解构与展开运算符(rest operator)结合使用来移除数组的第一个元素:
const numbers = [1,2,3];
const [, ...fooNumbers] = numbers;
fooNumbers; //=> [2, 3]
numbers; //=> [1,2,3]
除此之外,你也可以在遵循不可变原则的同时使用同样的方法来删除一个对象的属性。如下所示,删除big对象的foo属性:
const big = {
foo: "value foo",
bar: "value bar",
}
const { foo, ...small } = big;
small; //=> { bar: "value bar" }
big; //=>{ foo: "value foo", bar: "value bar" }
4. 解构可迭代的值
在前面几部分内容中,都是解构的数组。实际上解构运算是可以用于所有的可迭代对象的。
许多原生的基础类型和对象都是可迭代的,例如数组,类数组,字符串,set集合和map集合。
例如,你可以把字符串解构成单个字符:
const str = "cheese";
const [firstChar = ""] = str;
firstChar; //=> 'c'
当然解构不仅仅限于原生可迭代的那几种类型。解构可以被用于所有实现了迭代接口(iterable protocol)的对象。
如下所示,movies包含一个movie对象列表。我们想要解构movies对象的时候,可以获取到电影的title这个字符串。实现这个操作首先需要自定义一个迭代器:
const movies = {
list: [
{ title: "Heat" },
{ title: "Interstellar" },
],
[Symbol.iterator]() {
let index = 0;
return {
next: () => {
if (index < this.list.length) {
const value = this.list[index++].title;
return { value, done: false };
}
return { done: true }
}
}
}
}
const [firstMovieTitle] = movies;
console.log(firstMovieTitle); //=> 'Heat'
movies对象通过定义Symbol.iterator方法实现了一个迭代器。这个迭代器可以迭代所有电影的title属性。
我们在movies对象上遵循了迭代接口实现,从而实现了通过解构movies来获取到标题,比如我们获取第一个电影的标题:const [firstMovieTitle] = movies; 。
解构用法的上限就是没有上限。
5. 解构动态属性
function greet( obj, nameProp ) {
const { [nameProp]: name="Unknow" } = obj;
return `Hello, ${name}!`;
}
greet({ name: "Batman" }, "name"); //=> Hello, Batman!
greet( {}, "name" ); //=> Hello, Unknow!
greet()被调用时需要传递两个参数,一个是对象,一个是属性名称。
在greet()函数内部,解构表达式const { [nameProp]: name=“Unknow” } = obj;使用中括号[nameProp]读取动态属性的名称。name变量接收动态属性的值。
更好的做法就是你可以指定一个默认的值Unknow以防属性不存在的情况。