ES6——变量的解构赋值

385 阅读7分钟

数组的解构赋值

  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]

解构赋值的用处

解构赋值可以用于解构对象和数组的属性并得到属性值。

  1. 解构json对象
  2. 解构Set和Map数据解构
  3. 解构对象和包装类型对象的原始方法