ES6之变量的解构赋值

什么是解构赋值

以前,如果我们想给一个变量赋值,通常是这样(在Objective-C等语言中,现在仍然是如此):

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

ES6现在支持了类似这样的用法:

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

从而大大简化了变量赋值的语法,而且为诸如:Json解析、函数默认值用法提供了支持,文章最后会介绍。

JS的变量解构赋值,实际上的一种模式匹配,比如:{模式1} = {模式2},如果模式1能够部分或者完全匹配模式2,则匹配成功的模式1的部分变量就会被赋值为匹配到的值,否则赋值为undefined。这是解构赋值的核心思想,后面的各种类型的解构赋值其实都是这种思想的具体体现。

另外解构赋值,要求赋值对象,即等号右边的值,一定是一个可以遍历的结构,即符合Iterator接口。

接下来会依次介绍以下几种用法:

  • 数组的解构赋值
  • 对象的解构赋值
  • 字符串的解构赋值
  • 数值、布尔值的解构赋值
  • 函数参数的解构赋值
  • 圆括号问题

在开始前,还有个关于nullundefined的小知识介绍下,js 中,null === undefined 是false。两者的区别在于:

  • null表示”没有对象”,即该处不应该有值。
  • undefined表示”缺少值”,就是此处应该有一个值,但是还没有定义

具体的可以看下阮一峰的文章:undefined与null的区别。下面的文章会用到这点。

数组的解构赋值

数组的结构赋值比较简单,我们下面通过一些不同的例子来看下:

正常结构

1
2
3
4
let [a,b,c] = [1,2,3];
a // 1
b // 2
c // 3

嵌套结构

1
2
3
4
5
let [a,[b,c],d] = [1,[2,3],4];
a // 1
b // 2
c // 3
d // 4

…语句

1
2
3
let [a, ...b] = [1,2,3];
a // 1
b // [2,3]

缺省变量

1
2
3
let [a, ,c] = [1,2,3];
a // 1
c // 3

解构不完全成功

1
2
3
4
let [a,b,c] = [1,];
a // 1
b // undefined
c // undefined

另外还有两个注意点:

如果右侧不是一个可遍历的结构,则会报错,如:

1
let [foo] = 1;//error

使用let,const,不可以重复定义变量,如:

1
2
let a ;
let [a,b] = [1,2];//error: Duplicate declaration

默认值

解构赋值是允许给变量默认值的,如果解构失败(模式匹配不上,或者赋值为undefined),则会使用默认值。如:

1
2
3
let [a = 1, b = 2] = [1,];
a // 1
b // 2

在前面章节,讲了nullundefined,如果赋值null,系统是不会使用默认值的,因为nullundefined是不严格相等的。如:

1
2
3
let [a = 1,b] = [1,null];
a // 1,
b // null,

另外默认值可以引用解构赋值的其他变量,前提是这个变量已经声明了,如:

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

对象的解构赋值

对象和数组的重要区别就是,前者是顺序的,后者是非顺序的,所以对象的解构赋值,更加能体现模式匹配的意义。

如何理解

我们先讲回数组的解构赋值,其实可以这样理解:

1
let [a, [b, c], d] = [1, [2,3], 4];

a可以理解为模式1,[b,c]可以理解为模式2, d可以理解为模式3
函数可以这样写

1
let [模式1,模式2,模式3] = [匹配模式1,匹配模式2,匹配模式3];

在数组中,模式的匹配是根据的因为数组是有序的,而在对象中,模式匹配则是根据模式的key。比如:

1
2
3
let {foo, bar} = {foo: 'fooValue', bar: 'barValue'};
foo // 'fooValue'
bar // 'barValue'

我们根据模式匹配的思想,其实等号左边就是{模式1,模式2},等号右边就是{模式1:模式1值,{模式2:模式2值}},所以上述代码的完整版是:

1
let {foo模式: foo模式实例foo, bar模式: bar模式实例bar} = {foo模式: foo模式实例'fooValue', bar: bar模式实例'barValue'};

这里我们一定要区分的就是模式名和模式实例,比如:

1
2
3
let {foo:[1,bar]} = {[1,2]};
bar // 2
foo // undefined

这里的foo是模式名,并不是变量,所以不会被赋值.

嵌套对象的注意点

有嵌套对象的时候,需要注意,如果子对象的父属性不存在,会报错,如:

1
let {foo: {bar} = {bar: 'bar'}};//TypeError: Cannot read property 'bar' of undefined

这中间的流程是这样的:

1
2
let _tmp = {bar: 'bar'};
_tmp.foo.bar;//error
已声明的变量赋值注意点

大括号的情况要注意,会被系统理解为代码块,发生错误:

1
2
let x ;
{x} = {x: 1};

在外面加上圆括号可以解决:

1
2
let x ;
({x} = {x: 1});

字符串的解构赋值

字符串在被解构赋值的时候,会被转换成类似数组的对象:

1
2
3
4
5
let [a,b,c,d] = 'hello';
a // 'h'
b // 'e'
c // 'l'
d // 'l'
属性解构

这里要介绍个好玩的东西,解构不止能解构值,还能解构属性,因为这里用的都是.语法,比如:

1
2
3
4
let {length: len} = 'hello'
这里最终执行的是:
let _tmp = ['h','e','l','l','o'];
len = _tmp.length;//这里本来是要报错的,但是_tmp恰好有length属性,所以len 是 5

数值和布尔值的解构赋值

在数值和布尔值的情况下,等号右边会先转换成对象。

比如:

1
2
3
let {foo} = 123;
实际执行是:
let foo = 123.foo;

这里还是可以用属性解构:

1
2
let {toString: s} = 123;
s // function toString() { [native code] }

这里的数值和布尔值的结构赋值,我目前不太了解具体的实际用法,如果读者知道,麻烦告诉我哈。

函数参数的结构赋值

这里和对象的解构赋值一模一样,不具体介绍

圆括号问题

学过编译原理的人都知道,编译原理到底有多变态,所以编译器的感受应该和我们一样(笑)。在遇到()的时候,编译器也会出现问题,所以这里有个原则:

能不适用圆括号,就不要使用

除非,满足两个条件

  • 不是定义变量,而是赋值
  • 不在模式部分适用

比如:

1
2
{p:(a)} = {p : 'a'};
a // 'a'

其他的都会报错

用途

解析多个值

在使用函数的时候,经常会返回多个值(js中常见,其他的还真的不常见),用解构赋值的话,语法就会非常简单:

1
let {a,b,c} = foo();

函数的参数和默认值

1
function foo ({a = 1,b = 2,c = 3}){};//

这里可以节省判断入参的代码

和map结合

1
2
3
4
5
6
7
8
let map = new Map();
map.set("key1","value1");
map.set("key2","value2");
for (let [a, b] of map) {
console.log('key is ' + a, 'value is '+ b);
}

载入模块使用

1
import {foo1,foo2} from 'react-native';

这里的用法其实大都差不多,主要是用来简化代码,提高可读性。