一、数组的解构赋值
1.1 用法
以前,为变量赋值,只能直接指定值。
let a = 1;
let b = 2;
ES6 允许写成下面这样。
let [a, b] = [1, 2];
a // 1
b // 2
只要等号左右俩边的格式一样,变量会对应被赋值,就是解构成功了。
let [a, [b]] = [1, [2]];
a // 1
b // 2
let [, c] = [2, 3];
c // 3
let [a, , c] = [1, 2, 3];
a // 1
c // 3
let [a, ...arr] = [1, 2, 3];
a // 1
arr // [2, 3]
如果解构不成功(右边不完全满足左边格式),变量的值就等于undefined。
let [a] = [];
console.log(a)
// undefined
let [a, b] = [1];
// undefinedundefined
但是当右边的格式和左边类似,只是数量上更多,则会被解构成功,但是只是部分解构,结果是成功。
let [a, b] = [1, 2, 3];
a // 1
b // 2
let [a, [b], c] = [1, [2, 3], 4];
a // 1
b // 2
c // 4
对于 Set 结构,也可以使用数组的解构赋值(毕竟 Set 也是特殊的数组嘛)。
let [a, b, c] = new Set([1, 2, 3]);
a // 1
事实上,只要某种数据结构具有 Iterator
接口,都可以采用数组形式的解构赋值。具体的等学到 Iterator
再说吧。
1.2 默认值
解构赋值允许指定默认值。
let [foo = true] = [];
foo // true
let [a, b = 2] = [1];
// a = 1, b = 2
let [a, b = 2] = [1, undefined];
// a = 1, b = 2
好像不难理解吧,挺简单的,接下来再看一个。
function f() {
return 1;
}
let [a = f()] = [2];
a // 2
其实也是可以用函数对其赋值,但是优先确定是否有结构赋值,有的情况下优先结构赋值,没有的话再计算函数后赋默认值。
二、对象的解构赋值
2.1 用法
let { a, b } = { a: 1, b: 2 };
a // 1
b // 2
再看一个:
let { c } = { a: 1, b: 2 };
c // undefined
let { b } = { a: 1, b: 2 };
b // 2
上面的数组解构,按样子写,c
会被第一个右边值赋予,但是在对象结构中,只有赋值左右同样名字的值(且不考虑左右顺序),c
在右边的对象中没有定义,则左边也就无法取到值。对象的解构赋值,可以很方便地将现有对象的方法,赋值到某个变量。
let { log, sin, cos } = Math;
是不是很方便!!!但是如果变量名与属性名不一致,必须写成下面这样。
let { a: c } = { a: 1, b: 2 };
c // 1
let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'
这样写,真的累,但是也能看到它到底是怎么解构赋值的:
let { a: a, b: b} = { a: 1, b: 1 };
它是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。
let { a: c } = { a: 1, b: 2 };
c // 1
a // error: a is not defined
上面代码中,a
是匹配的模式,c
才是变量。真正被赋值的是变量 c
,而不是模式 a
。
与数组一样,解构也可以用于嵌套结构的对象。
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"
注意,这时 p
是模式,不是变量,因此不会被赋值。如果 p
也要作为变量赋值。
但是如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。
// 报错
let {a: {b}} = {c: 3};
上面代码中,等号左边对象的a
属性,对应一个子对象。该子对象的b
属性,解构时会报错。原因很简单,因为a
这时等于undefined,再取子属性就会报错。
注意,对象的解构赋值可以取到继承的属性。
const obj1 = {};
const obj2 = { foo: 'bar' };
Object.setPrototypeOf(obj1, obj2);
const { foo } = obj1;
foo // "bar"
上面代码中,对象obj1的原型对象是obj2。foo属性不是obj1自身的属性,而是继承自obj2的属性,解构赋值可以取到这个属性。
2.2 默认值
对象的解构也可以指定默认值。
var {x = 3} = {};
x // 3
var {x, y = 5} = {x: 1};
x // 1
y // 5
var { message: msg = 'Something went wrong' } = {};
msg // "Something went wrong"
注意:默认值生效的条件是,对象的属性值严格等于 undefined
。
var {x = 3} = {x: undefined};
x // 3
var {x = 3} = {x: null};
x // null
上面代码中,属性 x
等于 null
,因为 null
与 undefined
不严格相等,所以是个有效的赋值,导致默认值3不会生效。
三、字符串的解构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象。
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
类似数组的对象都有一个length属性,因此还可以对这个属性解构赋值。
let {length : len} = 'hello';
len // 5
四、数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象。
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
上面代码中,数值和布尔值的包装对象都有toString属性,因此变量s都能取到值。
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefined和null无法转为对象,所以对它们进行解构赋值,都会报错。
let { prop: x } = undefined; // TypeError
let { prop: y } = null; // TypeError
五、用途
变量的解构赋值用途很多。
(1)交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
上面代码交换变量x和y的值,这样的写法不仅简洁,而且易读,语义非常清晰。
(2)从函数返回多个值
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
function example() {
return {
a: 1,
b: 2
};
}
let { a, b} = example();
(3)函数参数的定义
解构赋值可以方便地将一组参数与变量名对应起来。
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
(4)提取 JSON 数据
let jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
};
let { id, status, data: number } = jsonData;
console.log(id, status, number); // 42, "OK", [867, 5309]
(5)函数参数的默认值
jQuery.ajax = function (url, {
async = true,
beforeSend = function () {},
cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
} = {}) {
// ... do stuff
};
(6)遍历 Map 结构
const map = new Map();
map.set('first', 'hello');
map.set('second', 'world');
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// first is hello
// second is world
如果只想获取键名,或者只想获取键值,可以写成下面这样。
for (let [key] of map) {
// ...
}
for (let [,value] of map) {
// ...
}
(7)输入模块的指定方法
加载模块时,往往需要指定输入哪些方法。解构赋值使得输入语句非常清晰。
const { SourceMapConsumer, SourceNode } = require("source-map");