深入浅出讲解ES6的解构
什么是解构?
解构与构造数据截然相反。例如,它不是构造一个新的对象或数组,而是逐个拆分现有的对象或数组,来提取你所需要的数据。
ES6使用了一种新模式来匹配你想要提取的数值,解构赋值就是采用了这种模式。该模式会映射出你正在解构的数据结构,只有那些与该模式相匹配的数据,才会被提取出来。
被解构的数据项位于赋值运算符=的右侧,可以是任何数组和对象的组合,允许随意嵌套。用于给这些数据赋值的变量个数不限。
数组解构
数组解构使用一个数组作为一个数据项,你可以根据数组模式(用于从数组中匹配你所需要的数值)从这个数组里面提取数值给一个或者多个变量赋值。
数组模式是根据数值的位置来鉴别哪些值是你想要提取的。它必须能精确地映射数组的结构,来要让数组模式中的每个变量都被赋上被解构数组中位置与之相对应的值。
举几个例子来帮助我们理解吧:
数组模式示例
把数组中所有的数值赋给一个个单独的变量
//设置数组 constavengers=['TonyStark','SteveRogers','NatashaRomanoff']; //把数组解构赋值给变量。数组模式位于赋值运算符`=`的左侧,被结构的数组在 //其右侧。 const[ironMan,cap,blackWidow]=avengers; //ironMan='TonyStark' //cap='SteveRogers' //blackWidow='NatashaRomanoff' //输出ironMan: ironMan;
提取除第一个外的所有数值
constavengers=['TonyStark','SteveRogers','NatashaRomanoff']; //我们不用用到Tony const[,cap,blackWidow]=avengers; //ironMan=Error:undefined //cap='SteveRogers' //blackWidow='NatashaRomanoff' //输出cap: cap;
提取除第二个外的所有数值
constavengers=['TonyStark','SteveRogers','NatashaRomanoff']; //cap缺失 const[ironMan,,blackWidow]=avengers; //ironMan='TonyStark' //cap=Error:undefined //blackWidow='NatashaRomanoff' //输出blackWidow: blackWidow;
提取除最后一个外的所有数值
constavengers=['TonyStark','SteveRogers','NatashaRomanoff']; //ironManvscap const[ironMan,cap]=avengers; //ironMan='TonyStark' //cap='SteveRogers' //blackWidow=Error:undefined //输出blackWidow: ironMan;
嵌套数组
这种匹配模式也支持嵌套数组,只要保证赋值运算符=左侧的数组模式与右侧的数组结构相匹配即可。再次说明一下,=左边的变量都会被赋上=右侧数组中位置与之相对应的值。无论你怎么深层次地嵌套,仍可以对它们进行解构。
解构嵌套的数组
//DestructuringNestedArrays constavengers=[ 'NatashaRomanoff', ['TonyStark','JamesRhodes'], ['SteveRogers','SamWilson'] ]; //Avengersandtheirpartners const[blackWidow,[ironMan,warMachine],[cap,falcon]]=avengers; //blackWidow='NatashaRomanoff' //ironMan='TonyStark' //warMachine='JamesRhodes' //cap='SteveRogers' //falcon='SamWilson' //OutputwarMachine: warMachine;
从深层嵌套的数组中提取一个值
//从该数组中提取PepperPotts constavengers=[ 'NatashaRomanoff', [['TonyStark','PepperPotts'],'JamesRhodes'], ['SteveRogers','SamWilson'] ]; //Destructure const[,//跳过'NatashaRomanoff' [[,//跳过'TonyStark' hera//PepperPotts赋值给变量'hera' ]]]=avengers; //请注意:你也可以这样写 //const[,[[,hera]]]=avengers; //输出hera: hera; //hera='PepperPotts'
运用rest操作符捕获所有剩余项
如果你想要获取特定的数组项,并且把剩余的项归在一个数组,那么你可以这样运用rest操作符来解构:
//通过rest操作符解构 constavengers=['NatashaRomanoff','TonyStark','SteveRogers']; const[blackWidow,...theOthers]=avengers; theOthers; //blackWidow='NatashaRomanoff' //theOthers=['TonyStark','SteveRogers'] //输出theOthers: theOthers;
对象解构
对象解构就更神奇了,尤其是当你需要从一个复杂的、深层嵌套的对象中取值时,其作用更加明显。重申一下,对象解构与数组解构用的是同样的规则(即在赋值运算符左侧创建一个对象模式,使它的变量位置与=右侧对象的值位置相匹配)。
在对象解构中,你需要指明那些需要被提取值的属性名称,以及将要被赋值的变量名。跟数组解构一样,我们需要在赋值运算符左边先创建一个对象模式来映射被解构的对象。
尽管在这种情况下,我们想要提取的是对象属性的值(如:我们从{prop:value}中提取value)。相应地,我们的对象模式必须有一个变量,这个变量的位置要跟我们即将提取的属性值所在的位置一致。
简单示例
提取一个简单的对象属性值
我们可以这样做,来将对象{ironMan:'TonyStark'}的属性ironMan的值'TonyStark'赋值给变量a:
//解构对象的属性值,赋给单个变量`a`: const{ironMan:a}={ironMan:'TonyStark'}; //输出a: a;//a='TonyStark'
提取多个属性值
我们只要拓展相同的模式,就可以从一个对象中提取多个属性值,如下:
//Setupourobject constavengers={ ironMan:'TonyStark', cap:'SteveRogers', blackWidow:'NatashaRomanoff' }; //Destructureobjecttoindividualvariables const{ ironMan:a, cap:b, blackWidow:c }=avengers; //a='TonyStark' //b='SteveRogers' //c='NatashaRomanoff' //Outputa: a;
观察一下这个解构模式是怎么确切地匹配被解构对象的。
嵌套的对象解构
像解构嵌套数组一样,我们可以对嵌套对象进行解构,不管它的层级多深。
//Setupourobject constavengers={ blackWidow:'NatashaRomanoff', ironManCharacters:{ couple:{ ironMan:'TonyStark', hera:'PepperPotts', }, partner:{ warMachine:'JamesBrodie' } }, capCharacters:{ cap:'SteveRogers', partner:{ falcon:'SamWilson' } } }; //Destructureobjecttoindividualvariables const{ blackWidow:a, ironManCharacters:{ couple:{ ironMan:b, hera:c }, partner:{ warMachine:d } }, capCharacters:{ cap:e, partner:{ falcon:f } } }=avengers; //a='NatashaRomanoff' //b='TonyStark' //c='PepperPotts' //d='JamesBrodie' //e='SteveRogers' //f='SamWilson' //Outputa: a;
给赋值的变量命名
当然,把变量名设为诸如a,b,c之类,是很糟糕的,变量名称应该是有意义的。
冗长式命名
//Setupourobject constavengers={ ironMan:'TonyStark', cap:'SteveRogers', blackWidow:'NatashaRomanoff' }; //Destructureobjecttoindividualvariableswithmeaningfulnames const{ ironMan:ironMan, cap:cap, blackWidow:blackWidow }=avengers; //blackWidow='NatashaRomanoff' //ironMan='TonyStark' //cap='SteveRogers' //OutputblackWidow: blackWidow;
这种做法比上面用a,b,c命名好,但是仍然可以完善。{ironMan:ironMan}看起来有点丑而且不直观。
语法上命名捷径
如果你要把一个对象的属性值赋给一个变量,该变量的名称跟对象的属性名称一样,那么在=左侧的赋值模式里面,你只需要简单地写属性名即可,如下:
//Setupourobject constavenger={ ironMan:'TonyStark' }; //Destructureobjecttoindividualvariableswithmeaningfulnames const{ ironMan//equivalentto'ironMan:ironMan' }=avenger; //ironMan='TonyStark' //OutputironMan: ironMan;
由于被解构的对象属性名称跟被赋值的变量名称相同,我们只需要把名称列出来一次即可。
语法简洁
我们稍微重新修整下前面的代码,就可以使它们看起来更加简洁明了:
//Setupourobject constavengers={ ironMan:'TonyStark', cap:'SteveRogers', blackWidow:'NatashaRomanoff' }; //Destructureobjecttoindividualvariableswithmeaningfulnames const{ironMan,cap,blackWidow}=avengers; //OutputironMan: ironMan;
从对象中提取一个深层嵌套的属性
当我们要提取一个深层嵌套的对象属性时,事情就更有趣了:
//Setupourobject constavengers={ blackWidow:'NatashaRomanoff', ironManCharacters:{ couple:{ ironMan:'TonyStark', hera:'PepperPotts', }, partner:{ warMachine:'JamesBrodie' } }, capCharacters:{ cap:'SteveRogers', partner:{ falcon:'SamWilson' } } }; //Destructureadeeplynestedobject const{ironManCharacters:{couple}}=avengers; //couple={ //ironMan:'TonyStark', //hera:'PepperPotts', //} //Outputcouple: couple;
等等,你是怎么阅读这段代码的?couple这个变量又是怎么被定义的呢?
通过这样拆分,我们就可以看出赋值运算符=左侧是被解构对象的一个映射:
constavengers={ ironManCharacters:{ couple:{ ironMan:'TonyStark', hera:'PepperPotts', } } }; const{ ironManCharacters:{ couple } }=avengers; //Outputcouple: couple;
仅仅使用const{couple}=avengers;并没有办法提取出couple的值。只有把要提取的对象属性的位置和名称映射出来,JS编译器才能得到相应的信息,沿着对象的所有属性往下查找,并准确地提取我们想要的值。
这里也要注意到couple用了语法捷径给变量命名,实际上是这样的:
const{ ironManCharacters:{ couple:couple } }=avengers;
couple就是这样被定义的,它的值就是对象avengers中属性名为couple的值。
给对象的属性解构赋值
到目前为止,我们都是解构对象的值来给单个的变量赋值,其实还可以给另一个对象的属性赋值。
constavengers={ blackWidow:'NatashaRomanoff', ironManCharacters:{ couple:{ ironMan:'TonyStark', hera:'PepperPotts' } } }; constironManProperties={ family:{} }; ({ ironManCharacters:{ couple:ironManProperties.family } }=avengers); ironManProperties.family //ironManProperties.family={ //ironMan:'TonyStark', //hera:'PepperPotts' //} //OutputironManProperties.family: ironManProperties.family;
在这里我们把ironManCharacters.couple的值赋给了ironManProperties.family这个属性,这里有两点需要说明一下:
1.解构赋值必须被包含在圆括号内
当我们在对一个已存在的变量(如上面例子中的ironManProperties)进行解构时,一定要这样做,而不是去声明一个新的变量。
2.模式仍然相匹配
{ironManCharacters:{couple...}}与对象avengers中的ironManCharacters相匹配。这样就能如你所愿,从avengers对象中提取出ironManCharacters.couple的值了。但是现在,couple后面放置了一个新的对象ironManProperties和它的属性family,其实被赋值的就是这个对象的属性ironManProperties.family了。
当你尝试把这种情况解释清楚时,是否还有所困惑呢?在jsfiddle里面尝试上面的代码,一切就明了了。
如果你不清楚自己为什么要这样做,请参考下一篇文章的例子。这些例子会告诉你,为什么采用这种模式来解构API调用的JSON对象,让你领略解构的神奇之处!
默认值
解构时,你还可以给变量指定一个默认值:
//Setupourobject constavengers={ ironMan:'TonyStark', cap:'SteveRogers', blackWidow:'NatashaRomanoff' }; //Destructureusingdefaults const{ironMan,cap,blackWidow,theHulk='BruceBanner'}=avengers; //ironMan='TonyStark' //cap='SteveRogers' //blackWidow='NatashaRomanoff' //theHulk='BruceBanner' //OutputblackWidow: blackWidow;
解构时要避免出现这些问题
解构赋值时没有使用const,let,var
在讲到对对象属性进行解构赋值时就已经提及了这一点,但这里还是有必要再重申一下,让大家有个深刻的印象。
不能对已经声明的变量进行解构
也就是说,你只能在对变量解构赋值的同时声明变量。
//Setupourobject constavengers={ ironMan:'TonyStark', cap:'SteveRogers', blackWidow:'NatashaRomanoff', theHulk:'BruceBanner' }; //Validdestructuring const{ironMan}=avengers; let{cap}=avengers; var{blackWidow}=avengers; //Invaliddestructuring lettheHulk; {theHulk}=avengers; //Error //OutputtheHulk: theHulk;
为何不能对一个已经声明的变量进行解构呢?那是因为这时如果你使用了花括号{,JavaScript会认为你是在声明一个block。
解决的办法就是把整个解构赋值用一对圆括号括起来。
如何对一个已声明的变量进行解构赋值
//Setupourobject constavengers={ ironMan:'TonyStark', cap:'SteveRogers', blackWidow:'NatashaRomanoff', theHulk:'BruceBanner' }; //AvalidHulk lettheHulk; ({theHulk}=avengers); //theHulk='BruceBanner' //OutputtheHulk: theHulk;
现在我们不是以花括号开头,所以JS不会认为我们是在声明一个block,这样就可以达到预期的解构结果。
直接返回一个被解构的值
在没有先声明一个接下来要被返回的变量时,就直接返回一个被解构的值,这样是无法达到预期效果的。例如,下面的代码中,返回的将是整个ironMan对象,而不是预期要的值TonyStark。
//Note:thisdoesn'twork! functiongetTonyStark(avengers){ return{ironMan:{realName}}=avengers; //returntheavengersobject,nottherealNamevalue } constavengers={ ironMan:{ realName:'TonyStark' } }; consttonyStark=getTonyStark(avengers); //tonyStark={ //ironMan:{ //realName:'TonyStark' //} //}; //OutputtonyStark: tonyStark;
要从一个被解构的对象中提取值,必须先把它赋值给一个变量,然后再把这个变量返回,如下代码所示:
//Note:thisDOESwork! functiongetTonyStark(avengers){ const{ironMan:{realName}}=avengers; returnrealName; } constavengers={ ironMan:{ realName:'TonyStark' } }; consttonyStark=getTonyStark(avengers); //tonyStark='TonyStark' //OutputtonyStark: tonyStark;
这种把赋值和返回分成两行代码的做法实在惹人厌烦,代码丑陋,也显得没必要。但很不幸,JavaScript就是这样工作的----你必须先把解构的值赋给一个变量,然后再把它返回,两步必须分开做。
但是,没有说我们只是说分开做,并没有说一定要摆成两行代码,所以像下面这样写成一行,也是能达到预期效果的:
functiongetTonyStark(avengers){ return({ironMan:{realName}}=avengers)&&realName; } constavengers={ ironMan:{ realName:'TonyStark' } }; consttonyStark=getTonyStark(avengers); //tonyStark='TonyStark' //OutputtonyStark: tonyStark;
由于JavaScript的_short-circuit_逻辑操作符(&&and||)会基于第一个操作数的值来返回第二个操作数的值,所以这种写法能够达到预期效果。这里,第一个操作数是解构赋值表达式,把值赋给realName。而realName也就是第二个操作数,所以它的值最终被返回。
这样做不是最佳的,但是能实现。在追求代码简短的同时,一定要注意代码的可读性。
总结
本文深入讲解了解构赋值的主要原则。解构不仅能减少你的代码量,还能从根本上改变你的编码方式。用的越多,你就会发现越多塑造数据和函数的方式,这些实现方式在过去几乎是不可能的。希望本文对大家学习ES6有所帮助。