数组的解构赋值
ES6允许按照一定模式从数组和对象中提取值,然后对变量进行赋值,这个操作被称为解构。
以前,为变量赋值只能指定值。
let a = 1;
let b = 2;
let c = 3;
ES6允许写成下面这样:
let [a, b, c] = [1, 2, 3]
上面的代码表示,可以从数组中提取值,按照对应位置对变量进行赋值。
本质上,这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。
let [foo, [[bar], [baz]]] = [1, [[2], [3]]]
foo // 1
bar // 2
baz // 3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [a, , c] = [1, 2, 3];
a // 1
b // 3
如果解构不成功,变量的的值就等于undefined
let [, foo] = [1];
foo // undefined
上述的情况是完全解构,即等号左边的模式和右边数组长度相同。另一种情况是不完全解构,即等号左边的模式只匹配一部分的等号右边的数组。
let [x, y] = [1, 2, 3]
x // 1
y // 2
let [a, [b]] = [1, [2, 3], 4]
a // 1
b // 2
上面的两个例子都属于不完全解构,但是仍可成功。 如果等号的右边不是数组,那么就会报错。
let [foo] = 1;
let [bar] = false;
let [baz] = {}
....
上面的语句都会报错,因为等号右边的值不是数组或转为对象以后不具有Iterator接口。
事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。
let [x, y, z] = new Set(["a", "b", "c"])
x // a
y // b
z // c
默认值
解构赋值允许指定默认值。
let [foo = false] = [];
foo // false
let [x, y = 'b'] = ["a", undefined];
x // a
y // b
注意ES6内部使用严格相等运算符判断一个位置是否有值。如果一个数组成员严格等于undefined,默认值才会生效
let [x = 1] = [undefined]
x // 1
let [y = 1] = [null]
y // null
上面的代码中,如果一个数组成员是null,默认值不会生效,因为null不严格等于undefined。
如果默认值是一个函数表达式,那么这个表达式是惰性求值的,只有在用到的时候才会求值。
function foo () {
console.log('aaa')
}
let [x = foo()] = [1]
x // 1
let [y = foo()] = [undefined]
y // aaa
默认值可以引用解构赋值的其他变量,但是该变量必需已经声明。
let [x = 1, y = x] = [] // x = 1, y = 1
let [x = 1, y = x] = [2] // x = 2, y = 2
let [x = 1, y = x] = [2, 3]; // x = 2, y = 3
let [x = y, y = 1] = [] // ReferenceError
最后一个表达式之所以会报错,是因为x用到默认值y时,y还没有声明。
对象的解构赋值
解构不仅可以用于数组,还可以用于对象。
let {foo, bar} = {foo: 'aaa', bar: 'bbb'}
foo // aaa
bar // bbb
对象的解构和数组的解构有个重要的不同。数组的元素是按次序排列的,变量的取值由位置决定,而对象的属性没有次序,变量必需和属性名相同才能取到正确的值。
let {foo, bar} = {foo: 'aaa', bar: 'bbb'}
foo // aaa
bar // bbb
let {baz} = {foo: 'aaa', bar: 'bbb'}
baz // undefined
如果变量名与属性名不一致必须写成下面这样。
let {foo: baz} = {foo: 'aaa', bar: 'bbb'}
baz // aaa
foo // ReferenceError
上述例子说明,对象的解构赋值是以下形式的简写:
let {foo: foo, bar: bar} = {foo: 'aaa', bar: 'bbb'}
也就是说,对象的解构赋值内部机制是先找到同名属性,然后再赋值给对应的变量。真正被赋值的是后者,而不是前者。前者只是一个匹配的模式(或名字),后面的才是真正被赋值的。
与数组一样,解构也可以用于嵌套的对象。
let obj = {
p: [
'hello',
{y: 'world'}
]
}
let {p: [x, {y: z}]} = obj
x // hello
z // world
注意,上面的代码p是匹配的模式,不是变量,因此不会被赋值。如果p也要作为变量解构赋值,可以写成下面这样。
let obj = {
p: [
'hello',
{y: 'world'}
]
}
let {p, p: [x, {y: z}]} = obj
p // ['hello', {y: 'hello'}]
x // hello
z // world
下面是另外一个例子:
let node = {
loc: {
start: {
line: 1,
column: 5
}
}
}
let {loc, loc: {start}, loc: {start: {line, column: col}}} = node
loc // loc: {start: { line: 1, column: 5 }}
start // { line: 1, column: 5 }
line // 1
col// 5
上面的代码有三次解构赋值,分别对loc、start、line和column四个属性的解构赋值。需要注意的是,最后一次对line的解构赋值中,只有line是变量,其余loc、start、column都是匹配模式
当然对象的解构赋值和数组的解构赋值一样,都可以有默认值,默认值生效的条件是,对象的属性值严格等于undefined
let {x = 3} = {x: undefined}
x // 3
let {x = 3} = {x: null}
x // null
如果解构失败,变量的值就等于undefined
let {foo} = {bar: 'baz'};
foo // undefined
如果要将一个已声明的变量用于解构赋值必须要非常小心。
// 错误的写法
let x
{x} = {x: 1}
// Error
上面的代码会报语法错误,因为JavaScript引擎会将{x}理解成一个代码块,从而发生语法错误。只要不将大括号写在首航,避免JavaScript引擎将其解析为代码块才可以解决这个问题。
// 正确的写法
let x
({x} = {x: 1})
上面的代码将整个结构语句放在一个圆括号里面,这样就可以正常执行。
字符串的解构赋值
字符串也可以解构赋值。因为此时字符串被转换成一个类似数组的对象(类数组)。
let {a, b, c, d, e, f} = 'hello'
a // h
b // e
c // l
d // l
e // o
f // undefined
类数组还有一个属性就是length,因此还可以对这个属性进行解构赋值。
let {length: len} = 'hello'
length // 5
数值和布尔值的解构赋值
数值和布尔值解构赋值是,如果等号右边实数值和布尔值则会先转成对象(转为其包装类型Number和Boolean)。如果等号右边的值是undefined和null这类没有包装类型的对象时,则会报错。
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Number.prototype.toString // true
let {toString: s} = undefined; // Error
let {toString: s} = null; // Error
上面的代码中,数值和布尔值的包装类型都有toString属性,因此变量s都能取到值;但是undefined和null由于不存在其包装类型的对象,所以在解构赋值的时候就会报错。
函数参数解构赋值
函数参数也可以进行解构赋值。
function foo ([x, y]) {
return x + y
}
foo([1, 2])
上面的代码中,foo函数的参数表面上是一个数组,但是在传入参数的那一刻,数组参数被解构成了x和y。下面一个例子就能直观的感受:
[[1, 2], [3, 4]].map(([a, b]) => a + b)
// [3, 7]
除了参数是数组的情况可以解构,参数是对象的时候也可进行解构,并且都可以赋值默认值,默认值赋值规律是当解构的参数的值严格是undefined时,默认值才会生效。
function move ({x = 0, y = 0} = {}) {
return [x, y]
}
move({x: 3, y: 8}) // [3, 8]
move({x: 3}) // [3, 0]
move({}) // [0, 0]
move() // [0, 0]
上面的代码中,函数move的参数是一个对象,通过对这个对象进行解构,得到变量y的值。如果结构失败,x和等于默认值。 注意,下面的写法会得到不一样的结果:
function move ({x, y} = {x: 0, y: 0}) {
return [x, y]
}
move({x: 3, y: 8}) // [3, 8]
move({x: 3}) // [3, undefined]
move({}) // [undefined, undefined]
move() // [0, 0]
上面的代码为函数move的参数指定了默认值,而不是为变量x和y指定了默认值,所以会得到与之前写法不同的结果。 除此之外,undefined还会触发函数参数的默认值。如下:
[1, undefined, 2].map((x = 'yes') => x) // [1, 'yes', 2]
解构赋值的用处
解构赋值可以用于解构对象和数组的属性并得到属性值。
- 解构json对象
- 解构Set和Map数据解构
- 解构对象和包装类型对象的原始方法