ES6变量的解构赋值

什么叫做解构?解构就是按照一定的模式,从数组或者对象中提取到对应的元素,然后把这些元素复制给新的变量。这样的过程叫做解构赋值。其中要注意两个地方,第一是提取对象必须是数组或者对象。如果不是对象或者数组,ES6会先把它转换成对象。如果转换不了,如null,undefined,则会报错。表示不能够解构。第二解构的作用是为了我们方便赋值。这是ES6的新用法。我写这篇文章的时候,火狐是已经实现了的。但是谷歌还没有。

Destructuring allows us to extract values from arrays and objects (even deeply nested) and store them in variables with a more convenient syntax.


直观感受

先看两个简单的小例子,直观的感受下,解构的好处。
对于数组过去我们是这样的:

1
2
3
4
let arr = [1,2];
a = arr[0];
b = arr[1];
//a->1,b->2

现在我们是这样的:

1
2
let [a,b]=[1,2];
//same result

对于对象我们以前是这样的:

1
2
3
let obj = {username:'Seven',password:'123'};
let username = obj.username;
let password = obj.password;

现在我们这样就行了:

1
2
3
let obj = {username:'Seven',password:'123'};
let {username,password} = obj;
console.log(username);

有没有觉得很方便?:) ,注意这里的{}里面的起名必须和obj里面的键相同,否则得不到正确的值。


数组的解构赋值

总结下,当我们使用:

1
2
3
var [var1,var2,var3]=arr
const [var1,var2,var3]=arr
let [var1,var2,var3]=arr

的时候,我们就是在用数组的解构赋值啦。

刚刚讲到概念的时候提到,就算是even deeply nested的时候仍然可以是赋值的。比如说:

1
2
3
4
5
let arr = [2,3,[4,5],6];
let [two,three,[four,five],six]=arr;
console.log(four);//4
//或者可以这样写
let [two,three,[four,five],six]=[2,3,[4,5],6];

所以我们实际上可以这样理解,解构赋值这种写法属于“模式匹配”,只要等号两边的模式相同,左边的变量就会被赋予对应的值。

下面看几个例子:

1
2
3
4
let [,,three]=[1,,3] //three:3
let [x, , y] = [1, 2, 3];//x:1,y:3
let [one,...theothers]=[1,2,3,4];//one:1,theothers:[2,3,4]
let [x, y, ...z] = ['a'];//x:a,y:undefined,z:[]

如果这种解构不对应,没有成功,也就是不是模式匹配,则就是undefined,如下面两个例子:

1
2
let [two]=[];  //two:undefined
let [one,two]=[1]; //two:undefined

上面是数组的解构赋值,但要注意一个问题,如果右边不是数组的话,是会报错的。比如:

1
2
3
4
5
let [one]=1;
let [one]=null;
let [one]=false;
let [one]={};
let [one]=undefined;

都会报下面的错:TypeError: (intermediate value)['@@iterator'] is not a function

如果等号的右边不是数组(或者严格地说,不是可遍历的结构,参见《Iterator》一章),那么将会报错。上面的表达式都会报错,因为等号右边的值,要么转为对象以后不具备Iterator接口(前五个表达式),要么本身就不具备Iterator接口(最后一个表达式)。事实上,只要某种数据结构具有Iterator接口,都可以采用数组形式的解构赋值。–from 阮一峰-解构赋值


数组解构可以有默认值

如下面几个例子:

1
2
var [one=1]=[];//one:1
var [one,two=2]=[1];//one:1,two:2

意思就是如果后面有值,就是用后面的值,如果后面没有值,就看有没有默认的,有的话就用默认的。再否则的话,就是取不到值啦。
那么什么标准是有值,没值呢?ES6规定,如果严格等于(===)undefined,那么就是没有值,用默认值。
比如下面的情况:

1
2
let [x=1]=[undefined];
let [y=1]=[null];

这个时候的情况就是:x=1,y=null啦,因为null不严格等于undefined。

并且这个取值还是惰性的,也就是只要后面判断的值不是undefined就不会是理会前面的默认赋值。下面这个例子中:

1
2
3
4
function test(){
return '1';
}
let [f=test()]=[1];

此时f的值是1,因为不为undefined,所以test()这个函数根本不会执行。这叫做惰性取值。

再就是既然表达式可以(注意上面的例子并不是错的,只是因为后面有值,才不执行而已),那么变量也可以。但是变量一定还是要注意哪个原则,先声明后,才可以使用。所以下面的例子中:

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

对象的解构赋值

我们最开始的时候提到了:{}里面的起名必须和obj里面的键相同,否则得不到正确的值。
这也是数组和对象的解构赋值的最大区别。因为数组是顺序结构,所以都是按找顺序取值就行了。但是对象不是单纯的以栈的数据结构来存储数据的,它包括了堆和栈。所以象的属性没有次序,变量必须与属性同名,才能取到正确的值。

基本的对象的解构赋值,是长这个样子的:

1
2
let {A,B}={A:a,B:b}
let {B,A}={A:a,B:b}

上面的结果都是A=a,B=b;A,B的顺序没有影响,只要名字对应就行喽。

其实它内部是这样的:

1
let {A=A,B=B}={A:a,B:b};

所以如果比如我们想把a取出来,但是不想放在A这个名字里,可以向这样:

1
let {A=C,B=D}={A:a,B:b};

这个时候,我们打印处C,D的时候,就是值为a或者b。真正被赋值的是后面的这个C,D,如果没有C,D的时候赋值的才是A,B。

还要注意如果是用let或者const声明的话,这里的A,B,C,D都不能被赋值过,否则会报错。因为let和const中变量不能重新声明。如果是var还是可以的。

1
2
let C='test';
let {A=C,B=D}={A:a,B:b};//error

如果把第二个let去掉也是可以的。这要注意。

刚刚我们讲过数组嵌套时,只要对应就行。对象也是可以嵌套的。如下面的例子:

1
2
3
var obj = {get:['first',{inner:'second'}]};
var {get:[x,{inner}]};
console.log(x+" "+y);//first second

注意这里的get不会被赋值,它在此处代表模式。如果你尝试打印出get,会得到ReferenceError: p is not defined的错。

与数组的解构赋值一样,对象的解构赋值也可以使用默认参数。


对象解构也可以有默认值

1
2
3
4
5
6
7
8
9
var {one=1} = {};   //one:1
var {one,two=2}={1}; //one:1,two:2

var obj={username:'Seven',password:'123'};
var {username='匿名',password='null'}=obj; //username:Seven,password:123

var {username:name='游客',password='null'} = {};//name:游客,password:null

var {username}={wrong:'游客'};//username=undefined,解构失败,为undefined

上面这都是声明的时候直接赋值了。如果我们把声明和赋值分开,如下面的情况:

1
2
3
var username;
{username}={username:'Seven'} //wrong
({username}={username:'Seven'}) //true

会报语法错误,SyntaxError: expected expression, got '='这是由于{}在开头会被解析为代码块。解决方法是我们用()把它扩起来。如上面所示。

那么这个对象的解构赋值用途大吗?其实可好用了,比如:

1
2
3
4
5
6
7
8
9
10
11
var obj = {
username:'Seven',
password:'123',
highestScore:function(){
console.log(100);
},
lowestScore:function(){
console.log(60);
}
}
var {username,password,highestScore,lowestScore}=obj;

这样我们分别把普通值和方法赋值给了相应的变量。当我们打印处username的时候是Seven,当我们console.log(highestScore());的时候会得到100啦。


更多用途

上面我们已经提到了一种最简单的用途,其实还有很多其他的用途,比如:

可以在函数中用
1
2
3
4
function fn([x,y]){
return x*y;
}
console.log(fn([1,2]));
可以在循环中用
1
2
[[1,2,3],[4,5,6]].map(([a,b,c])=>a+b+c;) //[6,15]
[1,undefined,2].map((x='set')=>x) //1,'set',2

上面的这几个例子,可能你会说,如果我不用解构,用原始传参也可以啊,看了下面的几个,你就不会这么说了。:)

返回多个值

以前如果我们要返回多个值,就要把值包装成对象 或者数组,然后函数外面时候在拆分,这多麻烦。有了解构赋值,我们可以很容易就实现这一点。

1
2
3
4
5
6
7
8
9
function test(){
return [1,5,'Seven'];
}
var [a,b,c]=test();

function test1(){
return {first:'1',second:'2',str:'this is a string'};
}
var {first,second,str}=test1();

这样我们就很容易的把每个值,直接放到变量里了。但是还是要注意,使用对象的解构时,要名称对应。

将参数和值对应起来

以前我们在传参的时候,要时刻注意参数的对应。如果不对应,函数变量对应的值,就是不对的。

1
2
function test(a,b,c){}
test(1,2,3);

用数组的话是对应的。也是一组有次序的值。这个时候跟我们以前的用法没有太大的区别。

1
2
function test([a,b,c]){}
test([1,2,3]);

但是当是对象的时候,就不是这样了。对象可以是一组无序的参数。

1
2
function test({x,y,z}){}
test({y:1,x:2,z:4})

快速提取json对象中的值
1
2
3
4
5
6
var jsonobj = {
username:'seven',
password:'124',
hobbies:['travelling','singing']
}
var {username,password,hobbies} = jsonobj;

这个跟我们先前讲的highestScore(),有点类似。

默认参数

过去我们可能是这样的。

1
2
3
4
wechat = function(url,username,message){
username = username || 'Seven';
message = message || 'message is null';
}

现在我们这样就行了:

1
2
3
4
5
6
wechat = function(url,{
username='Seven',
message='message is null',
isLogin = 'no',
cache = 'no'
}){}

通过这样的传参形式,就避免了类似a=a||'test'这种写法。


总结

主要是把变量的解构赋值,这块给理解了下。ES6的新特性要在平时刻意的多用。等到以后完全被浏览器支持的时候,用起来就方便多了。解构赋值的用途还是很多的,我觉得很棒。