数组的解构赋值
基本用法
数组的元素是按顺序排列的,变量的取值是由位置决定的。如果解构不成功,变量的值就等于 undefined。
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [, , third] = ["foo", "bar", "baz"];
third // "baz"
let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]
let [x, y, ...z] = ["a"];
x // "a"
y // undefined
z // []
// 以下两种情况 foo 都等于 undefined
let [foo] = [];
let [bar, foo] = [1];
不完全解构,是指等号左边的模式只匹配到一部分的等号右边的数组,但是解构可以成功。如下:
let [x, y] = [1, 2, 3];
x // 1
y // 2
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
默认值
解构时允许指定默认值,当赋值数组对应的值严格等于(===) undefined 时,使用默认值进行赋值。默认值可以引用解构赋值的其他变量,但该变量必须已经声明。
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x = 'a', y = 'b'
let [x, y = 'b'] = ['a', undefined]; // x = 'a', y = 'b'
let [x = 1] = [null]
x // null
let [x = 1, y = x] = []; // x = 1; y = 1
let [x = 1, y = x] = [2]; // x = 2; y =2
let [x = 1, y = x] = [1, 2]; // x = 1; y = 2
let [x = y, y = 1] = []; // ReferenceError
对象的解构赋值
对象的属性没有排序,变量必须与属性同名才能取到正确的值。如果变量名与属性名不一致,必须采用"属性名:变量名"的写法。实际上,对象的解构赋值是先找到前者的同名属性,然后再赋值给后者对应的变量。结构赋值不会赋值继承自原型对象上的属性
let { bar, baz, foo } = { foo: "aaa", bar: "bbb" }; // 相当于下一句
let { bar: bar, baz: baz, foo: foo } = { foo: "aaa", bar: "bbb" };
bar // "bbb"
baz // undefined
foo // "aaa"
let { foo: first, bar: last } = { foo: "aaa", bar: "bbb" };
first // "aaa"
last // "bbb"
let { foo: baz } = { foo: "aaa", baz: "bbb" };
baz // "aaa"
foo // 报错,foo 未定义
如果解构失败,变量的值等于 undefined。对象解构时也可以指定默认值,默认值生效的条件是对象的属性值严格等于 undefined。
let { x: y = 3 } = { x: 5 };
y // 5
如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,将会报错
let { foo: { bar } } = { baz: "baz" } ;
将一个已经声明的变量用于解构赋值时,很容易出错。js 引擎会将下列的{x} 理解成代码块,从而发生语法错误,只有不将大括号放在行首,避免 js 引擎将其解释为代码块,才可以解决这个问题
1et x;
{x} = {x:1}; // 报错SyntaxError
({x} = {x:1}); // 成功
由于数组本身是特殊的对象,因此可以对数组进行对象属性结构
let arr = [1, 2, 3];
let { 0 : first, [arr.length - 1] : last } = arr;
first // 1
last // 3
字符串的解构赋值
字符串可以进行解构赋值,因为字符串被转换成一个类数组对象,并且包含一个 length 属性。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
let { length : len } = 'hello';
len // 5
数值和布尔值的解构赋值
解构赋值时,只要等号右边不是对象或数组,就会先将其转换为对象。undefined 和 null 无法转换为对象,因此解构时会报错。
let { toString: s } = 123;
s === Number.prototype.toString // true
let { toString: y } = true;
s === Boolean.prototype.toString // true
let { prop:x } = undefined; // TypeError
let { prop:z } = null; // TypeError
函数参数的解构赋值
函数参数的解构赋值可以使用默认值,undefined 就会触发函数参数的默认值
function move1({ x = 0, y = 0 } = {}) {
return[x, y];
}
move1({ x: 3, y: 8 }); // [3, 8]
move1({ x: 3 }); // [3, 0]
move1({}); // [0, 0]
move1(); // [0, 0]
function move2({ x, y } = { x: 0, y: 0 }){
return [x, y];
}
move2({ x: 3, y: 8 }); // [3, 8]
move2({ x: 3 }); // [3, undefined]
move2({}); // [undefined, undefined]
move2(); // [0, 0]
圆括号问题
不能使用圆括号的情况
-
变量声明语句
// 以下语句全部报错,变量声明语句,模式不能使用圆括号 let [(a)] = [1]; let { x: (c) } = {}; let ({ x: c }) = {}; let { (x: c) } = {}; let { (x): c } = {}; -
函数参数
函数参数也属于变量声明语句,因此也不能使用圆括号
// 以下函数参数声明都报错 function f([(z)]) { return z; } function f([z, (x)]) { return x; } -
赋值语句的模式
({ p: a }) = { p: 42 }; ([a]) = [5]; // 这两行赋值语句把整个模式放在圆括号中,导致报错 // 将一部分模式放在圆括号之中,导致报错 [({ p: a }), { x: c }] = [{}, {}];
可以使用圆括号的情况
可以使用圆括号只有一种情况:赋值语句 (注意和声明语句的区别) 的非模式部分可以使用圆括号。如下:
[(b)] = [3];
({ p: (d) } = {});
[(parseInt.prop)] = [3];
变量解构赋值的用途
-
交换变量的值
let x = 1; let y = 2; [x, y] = [y, x]; -
从函数返回多个值
function example1() { return [1, 2, 3]; } let [a, b, c] = example1(); function example2() { return { foo: 1, bar: 2 } } let { foo, bar } = example2(); -
函数参数的定义
function f1([x, y, z]) {} f1([1, 2, 3]); function f2({x, y, z}) {} f2({ y: 2, x: 1, z: 3 }); -
提取 JSON 数据
let json = { id: 42, name: "chen", year: 22, money: [666, 888] } let { id, name, year: age, money } = json; -
函数参数的默认值
-
遍历 Map 结构
Map 结构原生支持 Iterator 接口,配合变量的解构赋值获取键名和键值非常方便
var map = new Map(); map.set('first', 'hello'); map.set('second', 'world'); // 获取键名和键值 for(let [key, value] of map) { console.log(key + " is " + value); } // 获取键名 for(let [key] of map) {} // 获取键值 for(let [, value] of map) {} -
输入模块的指定方法
const { SourceMaoConsumer, SourceNode } = require("source-map");