数组的结构赋值
- 数组的结构赋值是按顺序赋值的
[a,b,c] = [1,2,3]
console.log(a,b,c)
[b,a,c] = [1,2,3]
console.log(a,b,c)
// 123
// 213
- 扩展运算符+结构赋值
let [head, ...tail] = [1, 2, 3, 4];
- 不完全解构
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
- 解构赋值必须为可遍历解构,即具有iterator接口
如果等号的右边不是数组(或者严格地说,不是可遍历的结构,参见《Iterator》一章),那么将会报错。
另外一方面,居于iterator接口的解构都可以使用结构赋值,Set和Map,甚至generator
let [x, y, z] = new Set(['a', 'b', 'c']);
x // "a"
function* fibs() {
let a = 0;
let b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
let [first, second, third, fourth, fifth, sixth] = fibs();
sixth // 5
- 结构赋值+默认值
ES6 内部使用严格相等运算符(===),判断一个位置是否有值。所以,只有当一个数组成员严格等于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
这里即便是null, null和undefined在‘==‘的情况下是相等的,但是不全等,所以上面代码x最后的值是null,而不是默认值1
如果默认值是函数表达式或者就是函数,那么这个表达式是惰性求值的,即只有在用到的时候,才会求值。
对象的结构赋值
- 对象的结构赋值等式右边也要是对象
由于解构赋值要求等号右边是一个对象,所以如果等号右边是undefined或null,就会报错,因为它们无法转为对象
let { x, y, ...z } = null; // 运行时错误
let { x, y, ...z } = undefined; // 运行时错误
- 解构赋值必须是最后一个参数,否则会报错。
let { ...x, y, z } = someObject; // 句法错误
let { x, ...y, ...z } = someObject; // 句法错误
- 对象的结构赋值是按属性名赋值的,与数组结构赋值不同,它与顺序无关
//说明对象解构赋值与顺序无关
let { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
- 对象解构赋值内部机制,模式
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量,如果没找到则为undefined
let { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };
上面代码左边的foo:foo,第一个foo是模式,第二个foo才是被赋值的变量
- 嵌套赋值
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p: [x, { y }] } = obj;
x // "Hello"
y // "World"
这里的p是模式,不是变量,因此不会被赋值。如果p也要作为变量赋值,可以写成下面这样。
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p, p: [x, { y }] } = obj;
x // "Hello"
y // "World"
p // ["Hello", {y: "World"}]
如果解构模式是嵌套的对象,而且子对象所在的父属性不存在,那么将会报错。
- 对象结构赋值+默认值
- 已经声明的变量用于解构赋值
//错误写法
let x;
{x} = {x: 1};
// SyntaxError: syntax error
// 正确的写法
let x;
({x} = {x: 1});
上面代码的写法会报错,因为 JavaScript 引擎会将{x}理解成一个代码块,从而发生语法错误。只有不将大括号写在行首,避免 JavaScript 将其解释为代码块,才能解决这个问题。
字符串的结构赋值
字符串也可以解构赋值。这是因为此时,字符串被转换成了一个类似数组的对象
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
函数参数结构赋值
- 参数结构赋值的默认值
第一种写法
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]
等式左边部分{x = 0, y = 0}这一块是默认值部分,中间的等号+右边的{}才是解构赋值部分并且还是传入参数部分,右边的{}也是默认值部分,默认传入参数为{}
这里逻辑有点复杂,我要好好解释一下
- move({x = 0, y = 0} = {})这部分使用了两次默认值,一次解构赋值
- 先说这个默认赋值{x = 0, y = 0} = {},如果外界调用参数的时候什么参数都没有传,那么参数默认使用{}(右边的{}),所以此时入参为默认是空对象
- 然后是第二次默认赋值,入参为空对象,赋值给左边的{x = 0, y = 0},之前写到过,如果默认赋值右边为严格的undefined,才会使用默认值,这符合条件,所以x,y为0,0
第二种写法
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]
看懂第一种写法之后,第二种写法就明白什么回事了,第二种写法只使用了一次默认值,即等号右边的{ x: 0, y: 0 },给传入参数默认赋值0,0,所以代码里面当我们传入参数为{x:3}的时候,因为有传参并且不为undefined,所以x得到了值3,而y没有值,所以是undefined。
解构赋值用途
- 交换变量的值
let x = 1;
let y = 2;
[x, y] = [y, x];
- 从函数返回多个值
// 返回一个数组
function example() {
return [1, 2, 3];
}
let [a, b, c] = example();
// 返回一个对象
function example() {
return {
foo: 1,
bar: 2
};
}
let { foo, bar } = example();
- 无序给函数传参
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3]);
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1});
- 提取 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]
- for…of…
for (let [key, value] of map) {
console.log(key + " is " + value);
}
// 获取键名
for (let [key] of map) {
// ...
}
// 获取键值
for (let [,value] of map) {
// ...
}
- require 模块时候
const { SourceMapConsumer, SourceNode } = require("source-map");