什么叫做解构?解构就是按照一定的模式,从数组或者对象中提取到对应的元素,然后把这些元素复制给新的变量。这样的过程叫做解构赋值。其中要注意两个地方,第一是提取对象必须是数组或者对象。如果不是对象或者数组,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
4let arr = [1,2];
a = arr[0];
b = arr[1];
//a->1,b->2
现在我们是这样的:1
2let [a,b]=[1,2];
//same result
对于对象我们以前是这样的:1
2
3let obj = {username:'Seven',password:'123'};
let username = obj.username;
let password = obj.password;
现在我们这样就行了:1
2
3let obj = {username:'Seven',password:'123'};
let {username,password} = obj;
console.log(username);
有没有觉得很方便?:) ,注意这里的{}里面的起名必须和obj里面的键相同,否则得不到正确的值。
数组的解构赋值
总结下,当我们使用:1
2
3var [var1,var2,var3]=arr
const [var1,var2,var3]=arr
let [var1,var2,var3]=arr
的时候,我们就是在用数组的解构赋值啦。
刚刚讲到概念的时候提到,就算是even deeply nested
的时候仍然可以是赋值的。比如说:1
2
3
4
5let 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
4let [,,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
2let [two]=[]; //two:undefined
let [one,two]=[1]; //two:undefined
上面是数组的解构赋值,但要注意一个问题,如果右边不是数组的话,是会报错的。比如:1
2
3
4
5let [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
2var [one=1]=[];//one:1
var [one,two=2]=[1];//one:1,two:2
意思就是如果后面有值,就是用后面的值,如果后面没有值,就看有没有默认的,有的话就用默认的。再否则的话,就是取不到值啦。
那么什么标准是有值,没值呢?ES6规定,如果严格等于(===)undefined,那么就是没有值,用默认值。
比如下面的情况:1
2let [x=1]=[undefined];
let [y=1]=[null];
这个时候的情况就是:x=1,y=null啦,因为null不严格等于undefined。
并且这个取值还是惰性的,也就是只要后面判断的值不是undefined就不会是理会前面的默认赋值。下面这个例子中:1
2
3
4function test(){
return '1';
}
let [f=test()]=[1];
此时f的值是1,因为不为undefined,所以test()这个函数根本不会执行。这叫做惰性取值。
再就是既然表达式可以(注意上面的例子并不是错的,只是因为后面有值,才不执行而已),那么变量也可以。但是变量一定还是要注意哪个原则,先声明后,才可以使用。所以下面的例子中:
1 | let [x=1,y=x]=[];//x=1,y=1 |
对象的解构赋值
我们最开始的时候提到了:{}里面的起名必须和obj里面的键相同,否则得不到正确的值。
这也是数组和对象的解构赋值的最大区别。因为数组是顺序结构,所以都是按找顺序取值就行了。但是对象不是单纯的以栈的数据结构来存储数据的,它包括了堆和栈。所以象的属性没有次序,变量必须与属性同名,才能取到正确的值。
基本的对象的解构赋值,是长这个样子的:1
2let {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
2let C='test';
let {A=C,B=D}={A:a,B:b};//error
如果把第二个let去掉也是可以的。这要注意。
刚刚我们讲过数组嵌套时,只要对应就行。对象也是可以嵌套的。如下面的例子:1
2
3var obj = {get:['first',{inner:'second'}]};
var {get:[x,{inner}]};
console.log(x+" "+y);//first second
注意这里的get不会被赋值,它在此处代表模式。如果你尝试打印出get,会得到ReferenceError: p is not defined
的错。
与数组的解构赋值一样,对象的解构赋值也可以使用默认参数。
对象解构也可以有默认值
1 | var {one=1} = {}; //one:1 |
上面这都是声明的时候直接赋值了。如果我们把声明和赋值分开,如下面的情况:1
2
3var username;
{username}={username:'Seven'} //wrong
({username}={username:'Seven'}) //true
会报语法错误,SyntaxError: expected expression, got '='
这是由于{}在开头会被解析为代码块。解决方法是我们用()把它扩起来。如上面所示。
那么这个对象的解构赋值用途大吗?其实可好用了,比如:1
2
3
4
5
6
7
8
9
10
11var 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 | function fn([x,y]){ |
可以在循环中用
1 | [[1,2,3],[4,5,6]].map(([a,b,c])=>a+b+c;) //[6,15] |
上面的这几个例子,可能你会说,如果我不用解构,用原始传参也可以啊,看了下面的几个,你就不会这么说了。:)
返回多个值
以前如果我们要返回多个值,就要把值包装成对象 或者数组,然后函数外面时候在拆分,这多麻烦。有了解构赋值,我们可以很容易就实现这一点。1
2
3
4
5
6
7
8
9function 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
2function test(a,b,c){}
test(1,2,3);
用数组的话是对应的。也是一组有次序的值。这个时候跟我们以前的用法没有太大的区别。1
2function test([a,b,c]){}
test([1,2,3]);
但是当是对象的时候,就不是这样了。对象可以是一组无序的参数。1
2function test({x,y,z}){}
test({y:1,x:2,z:4})
快速提取json对象中的值
1 | var jsonobj = { |
这个跟我们先前讲的highestScore(),有点类似。
默认参数
过去我们可能是这样的。1
2
3
4wechat = function(url,username,message){
username = username || 'Seven';
message = message || 'message is null';
}
现在我们这样就行了:1
2
3
4
5
6wechat = function(url,{
username='Seven',
message='message is null',
isLogin = 'no',
cache = 'no'
}){}
通过这样的传参形式,就避免了类似a=a||'test'
这种写法。
总结
主要是把变量的解构赋值,这块给理解了下。ES6的新特性要在平时刻意的多用。等到以后完全被浏览器支持的时候,用起来就方便多了。解构赋值的用途还是很多的,我觉得很棒。