ES6学习笔记(变量的解构赋值)

解构赋值这块,主要讲了数组,对象,字符串,数值和布尔值,函数参数这五类的解构赋值。

1. 结构赋值的用途

首先,我们先不看具体的用法,来看看结构赋值会运用到哪些地方,先有一个大概的印象。

交换变量的值

1
2

[x, y] = [y, x];

如上代码,这样写交换变量,简洁易懂,语义清晰。


从函数返回多个值

原来函数只能返回一个值,如果需要返回多个值需要把他们放到一个数组或者是对象里返回。有了解构赋值之后,一切就方便多了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

// 返回一个数组

function example() {
return [1, 2, 3];
}
var [a, b, c] = example();

// 返回一个对象

function example() {
return {
foo: 1,
bar: 2
};
}
var { foo, bar } = example();

函数参数的定义

1
2
3
4
5
6
7
8

// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3])

// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1})

提取JSON数据

解构赋值对提取JSON对象中的数据,尤其有用。

1
2
3
4
5
6
7
8
9
10
11

var jsonData = {
id: 42,
status: "OK",
data: [867, 5309]
}

let { id, status, data: number } = jsonData;

console.log(id, status, number)
// 42, OK, [867, 5309]

函数参数的默认值

1
2
3
4
5
6
7
8
9
10
11
12

jQuery.ajax = function (url, {
async = true,
beforeSend = function ()
{},

cache = true,
complete = function () {},
crossDomain = false,
global = true,
// ... more config
}) {
// ... do stuff
};

可以指定参数的默认值,避免了在函数体当中写var foo = config.foo || 'default foo';这样的语句。


遍历Map结构

任何部署了Iterator接口的对象,都可以用for...of循环遍历。Map结构原生支持Iterator接口,配合变量的解构赋值,获取键名和键值就很方便了。

1
2
3
4
5
6
7
8
9
10

var 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

如上,声明了两个变量keyvalue去接收了键值对。如果想只接收键名或者键值,可以运用如下写法。

1
2
3
4
5
6
7
8
9
10

// 获取键名
for (let [key] of map) {
// ...
}

// 获取键值
for (let [,value] of map) {
// ...
}

输入模块的制定方法

加载模块时,往往需要制定输入哪些方法,解构赋值使得输入语句非常清晰。(我没懂。。。。)

1
2

const { SourceMapConsumer, SourceNode } = require("source-map");

2. 数组的解构赋值

基本用法

按照一定的模式(格式),从数组和对象中取值来赋值给变量,这个过程被称为解构赋值。

以前,为变量赋值,只能是以下的形式:

1
2
3
4

var a = 1;
var b = 2;
var c = 3;

而ES6允许以这种形式来赋值:

1
2

var [a, b, c] = [1, 2, 3];

如上代码,等号两边可以按照对应位置,去对变量赋值。

这种写法属于“模式匹配”,只要等号两边的模式相同,右边的值就会赋给左边的变量中去。下面是一些解构的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3

let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

let [x, , y] = [1, 2, 3];
x // 1
y // 3

let [head, ...tail] = [1, 2, 3, 4];
head // 1
tail // [2, 3, 4]

let [x, y, ...z] = ['a'];
x // "a"
y // undefined
z // []

如果解构不成功,变量的值就为undefined

1
2
3

var [foo] = [];
var [bar, foo] = [1];

还有一种情况是不完全解构,等号左边的模式只可以匹配到一部分等号右边的数组,这种情况依然可以解构成功。

如果等号的右边不是一个可以遍历的解构(具体看这里),那么就会报错。

1
2
3
4
5
6
7
8

// 报错
let [foo] = 1;
let [foo] = false;
let [foo] = NaN;
let [foo] = undefined;
let [foo] = null;
let [foo] = {};

上面的式子都会报错,因为等号右边的值,要么转为对象以后不具备Iterator接口(前五个表达式),要么本身就不具备Iterator接口(最后一个表达式)。

解构赋值不仅适用于var命令,也适用于letconst命令。对于Set解构,也可以使用数组的解构赋值。

1
2
3

let [x, y, z] = new Set(["a", "b", "c"])
x // "a"

默认值

解构赋值允许指定默认值。

1
2
3
4
5
6

var [foo = true] = [];
foo // true

[x, y = 'b'] = ['a'] // x='a', y='b'
[x, y = 'b'] = ['a', undefined] // x='a', y='b'

这里要注意的是,如果要使用默认的值,一定要严责等于undefined,否则默认值是不会生效的。

1
2
3
4
5
6

var [x = 1] = [undefined];
x // 1

var [x = 1] = [null];
x // null

如上代码,如果等号右边是null的话,默认值就不会生效,因为null不严格等于undefiend

默认值可以引用解构赋值的其他变量,但该变量必须已经声明。

1
2
3
4
5

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

如果代码,最后一个表达式报错是因为在x用到y时,y还没有声明。


3. 对象的解构赋值

解构不仅可以用于数组,还可以用于对象。

1
2
3
4

var { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

对象的解构和数组不同的是,数组的解构是有顺序限制的,变量的取值是由他的位置决定的,而对象的属性没有次序,变量必须与属性同名才能取到正确的值。

1
2
3
4
5
6
7

var { bar, foo } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"

var { baz } = { foo: "aaa", bar: "bbb" };
baz // undefined

如上代码,等号左边两个变量的次序与等号右边两个属性的次序不一致,但是取值正确,如果变量没有对应的同名属性的话会取不到值。

如果变量名和属性名不一致,我们必须引用一个叫模式的东西,我可能理解的比较通俗,先看以下的代码,在做解释。

1
2
3
4
5
6
7
8

var { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"

let obj = { first: 'hello', last: 'world' };
let { first: f, last: l } = obj;
f // 'hello'
l // 'world'

对象的赋值解构的内部机制其实是这样的:

1
2

var { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };

:之前的部分我们叫做模式:之后的部分才是变量,在解构赋值内部机制是先根据模式找到同名的属性,再给对应的变量赋值,真正被赋值的是后面的变量。

在平时对象解构赋值的时候我们把:之后的变量给省略掉了,这样在内部他会加上一个和模式名字相同的变量名,在省略了变量名的情况下,我们打印模式名是不会出错的。

但如果变量名和模式名是不一样的时候,比如下面这种情况:

1
2
3
4

var { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined

如上这样,真正被赋值的就是变量baz,而不是模式foo

和数组一样,解构也可以用于嵌套解构的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

var node = {
loc: {
start: {
line: 1,
column: 5
}
}
};

var { loc: { start: { line }} } = node;
line // 1
loc // error: loc is undefined
start // error: start is undefined

如上,只有line是变量,locstart都是模式,不会被赋值。

嵌套赋值的例子:

1
2
3
4
5
6
7
8

let obj = {};
let arr = [];

({ foo: obj.prop, bar: arr[0] } = { foo: 123, bar: true });

obj // {prop:123}
arr // [true]

对象的解构也可以指定默认的值。

1
2
3
4
5
6
7
8
9
10

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。如果不是严格等于undefined默认值就不会生效。

1
2
3
4
5
6

var {x = 3} = {x: undefined};
x // 3

var {x = 3} = {x: null};
x // null

如果解构失败的话。变量的值也会等于undefined

1
2
3

var {foo} = {bar: 'baz'}
foo // undefined

如果,解构的模式是一个对象的话,而模式名却在等号的右边不存在的话,那么将会报错。因为在解构的时候,首先会寻找与模式同名的属性,如果没有这个属性名就会报错。

1
2
3

// 报错
var {foo: {bar}} = {baz: 'baz'}

4. 字符串的解构赋值

字符串解构赋值是,会转换为一个类似数组的对象。

1
2
3
4
5
6
7

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

5. 数值和布尔值的解构赋值

解构赋值时,如果等号右边是数值和布尔值,会先转为对象。

1
2
3
4
5
6

let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true

解构赋值的规则是,只要等号右边的值不是对象,就先将其转化为对象,但是由于undefinednull不能转为对象,所以对他们进行解构赋值都会报错。

6. 函数参数的解构赋值

1
2
3
4
5
6

function add([x, y]){
return x + y;
}

add([1, 2]) // 3

如上代码,函数add的参数实际上不是一个数组,而是通过结构得刀的变量xy

函数参数的解构也可以使用默认值。

1
2
3
4
5
6
7
8
9

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]

以上还有很多细节方面没有描述,只是在常用方面做出了总结,详细的请看