解构----深入理解ES6(二)

解构是一种打破数据结构,将其拆分为更小部分的过程。
一、为何使用结构功能
在ECMAScript5及早期版本中,我们为了从对象和数组中获取特定数据并赋值给变量,编写了许多看起来同质化的代码。如下:
let options = {
repeat:true,
save:flse
}
//从对象中提取数据
let repeat = options.repeat,
save = options.save;
如果要提取更多的变量,则必须编写类似的代码来为变量赋值,如果其中还包含嵌套结构,就更麻烦了。必须要深入挖掘整个数据结构才能找到所需数据。
所以ES6为对象和数组都添加了解构功能。
二、对象解构
对象解构的语法形式是在一个赋值操作符左边放置一个对象字面量。如:
let person = {
name:"zhang",
age:23
}
let {name,age} = person ;
console.log(name);//zhang
console.log(age);//23
上面这段代码中,person.name的值被存储在变量name中,person.age的值被存储在变量age中。
如果使用var、let或const解构声明变量,则必须要提供初始化程序(也就是等号右侧的值)。下面几行代码都会导致错误,都是缺少了初始化程序。
//语法错误
var {name,age};
let {name,age};
const {name,age;}
如果不使用解构功能,则var和let声明不强制初始化,但对const声明,无论如何必须提供初始化。
2.1 解构赋值
我们也可以在给变量赋值时使用解构语法。如,在定义变量后想要修改他们的值:
let person = {
name:'zhang',
age :20
},
name = 'lili',
age = 30;
//使用解构语法为多个变量赋值
({name,age} = person);
console.log(name);//zhang
console.log(age);//20
在这个示例中,声明变量name和age时初始化了一个值,在后面几行中,通过解构赋值的方法,从person对象中读取相应的值重新为这两个变量赋值。
注意:一定要用小括号包裹解构赋值语句,JavaScript引擎将一对开放的花括号视为一个代码块,而语法规定,代码块语句不允许出现在赋值语句左侧,添加小括号后可以将块语句转化为一个表达式,从而实现整个解构赋值的过程。
解构赋值表达式的值与表达式右侧的值相等。
这样,在任何可以使用值的地方都可以使用解构赋值表达式。想象一下给函数传递参数值的过程:
let node = {
type:"Identifier",
name:"foo"
},
type = 'zhang',
name = 'lili';
function outputInfo(value) {
console.log(value === node);//true
}
outputInfo({type,name}=node);
console.log(type);//Identifier
console.log(name);//foo
调用outputInfo()函数时传入了一个解构表达式,由于JavaScript表达式的值为右侧的值,因而此处传入的参数等同于node,且变量type和name被重新赋值,最终将node传入outputInfo()函数。
解构赋值表达式如果为null或undefined会导致程序抛出错误。因为任何尝试读取null或undefined的属性的行为都会触发运行时错误。
2.2 默认值
使用解构赋值表达式时,如果指定的局部变量名称在对象中不存在,那么这个局部变量会被赋值为undefined。
let person = {
name:'zhang',
age:23
}
let {name,age,sex} = person;
console.log(name);//zhang
console.log(age);//23
console.log(sex);//undefined
上面代码额外定义了一个局部变量sex,然后尝试为它赋值,然后在person对象上,没有对应名称的属性值,所以像预期中的那样将它赋值为undefined。
当指定的属性不存在时,可以随意指定一个默认值,在属性名称后添加一个等号和相应的默认值即可:
let person = {
name:'zhang',
age:23
}
let {name,age,sex='女'} = person;
console.log(name);//zhang
console.log(age);//23
console.log(sex);//女
在此示例中,为变量sex设置了默认值 ‘女’,只有当person上没有该属性或者该属性值为undefined时,该值才生效。
2.3 为非同名局部变量赋值
上述示例中,解构赋值使用的都是与对象属性同名的局部变量,例如 person.name的值被存储在了变量name中。但如果希望使用不同命名的局部变量来存储对象属性的值,ES6的一个扩展语法可以满足需求,这个语法与完整的对象字面量属性初始化很像。如:
let node = {
type:'identifier',
name:'foo'
}
let {type:localType,name:localName} = node;
console.log(localType);//identifier
console.log(localName);//foo
上面代码使用解构赋值来声明变量localType和localName,这两个变量分别包含node.type和node.name 属性的值。type:localType语法的含义是读取名为type的 属性并将其值存储在变量localType中。
当使用其他变量名进行赋值时也可以添加默认值,只需在变量名后添加等号和默认值即可:
let node = {
type:'identifier'
}
let {type:localType,name:localName = 'zhang'} = node;
console.log(localType);//identifier
console.log(localName);//zhang
上面代码中,由于node.name属性不存在,变量被默认赋值为‘zhang’。
2.4 嵌套对象解构
解构嵌套对象与对象字面量语法相似。
let node = {
type:'identifier',
name:'foo',
loc:{
start:{
line:1,
column:2
},
end:{
line:2,
column:4
}
}
}
let {loc:{start,end}} = node;
console.log(start.line);//1
console.log(end.column);//4
示例中,在解构模式中使用了花括号,其含义是在找到node对象中的loc属性后,应当深入一层继续查找start、end属性。上述解构示例中,所有冒号前的标识符都代表在对象中检索位置,其右侧为被赋值的变量名;如果冒号后面是花括号,则意味着要赋予的最终值嵌套在对象内部更深的层级中。
也可以使用一个与对象属性名不同的局部变量名。
let node = {
type:'identifier',
name:'foo',
loc:{
start:{
line:1,
column:2
},
end:{
line:2,
column:4
}
}
}
let {loc:{start:localStart}} = node;
console.log(localStart.line);//1
console.log(localStart.column);//2
示例中,node.loc.start 被存储在新的局部变量localStart中。
三、数组解构
数组解构使用数组字面量,且解构操作全部在数组内完成。而不是像对象字面量语法一样使用对象的命名属性。
let colors = ['red','blue','green'];
let [firstColor,secondColor] = colors;
console.log(firstColor);//red
console.log(secondColor);//blue
从colors数组中解构出了‘red’和'blue'这两个值,并分别存储在变量firstColor,secondColor中。在数组解构语法中,通过值在数组中的位置进行选取,且可以将其存储在任意变量中,未显示声明的元素都会直接被忽略。(过程中,数组本身不会发生任何变化)。
在解构模式中,也可以直接省略元素,只为想要的元素提供变量名。举个栗子,如果只想要数组中的第三个值,则不需要提供第一个和第二个元素的变量名称:
let colors = ['red','blue','green'];
let [,,thirdColor] = colors;
console.log(thirdColor);//green
thirdColor前的逗号是前方元素的占位符,无论数组中的元素有多少个,都可以采用这种方式提取。
3.1 解构赋值
数组解构也可以用于赋值上下文,但是不需要用小括号包裹表达式。
let colors = ['red','green','blue'],
firstColor = 'black',
secondColor = 'pink';
[firstColor,secondColor] = colors;
console.log(firstColor);//red
数组解构独特的用例:交换两个变量的值。
//ES5的写法
let a = 1,
b = 2,
temp;
temp = a;
a = b;
b = temp;
console.log(a);//2
console.log(b);//1
必须借助中间变量temp来实现。ES是这么实现的:
let a = 1,
b = 2;
[a,b] = [b,a];
console.log(a);//2
console.log(b);//1
[a,b] = [b,a]左侧是赋值语句,是一个解构模式;右侧是一个为交换过程创建的临时数组字面量。代码执行过程中,先解构临时数组,将b和a的值复制到左侧数组的前两个位置,最终结果是变量互换了它们的值。
如果右侧数组解构赋值表达式的值为null或undefined,则会导致程序抛出错误。
3.2 默认值
可以在数组解构赋值表达式中为数组中的任意位置添加默认值。当指定位置的属性不存在或者为undefined时,使用默认值。
let colors = ['red'];
let [firstColor,secondColor = 'green'] = colors;
console.log(firstColor);//red
console.log(secondColor);//green
3.3 嵌套数组解构
在原有的数组模式中插入另一个数组模式,即可将解构过程深入到下一层级。
let colors = ['red',['green','white'],'blue'];
let [firstColor,[secondColor1,secondColor2]] = colors;
console.log(firstColor);//red
console.log(secondColor1);//green
console.log(secondColor2);//white
示例中,变量secondColor1引用的是colors数组中的值‘green’,该元素包含在数组内部的另一个数组中,所以secondColor1两侧的方括号是一个必要的解构模式。
3.4 不定元素
在数组中,可以通过...语法将数组中的其余元素赋值给一个特定的变量。
let colors = ['red','green','blue'];
let [firstColor,...restColors] = colors;
console.log(firstColor);//red
console.log(restColors.length);//2
console.log(restColors[0]);//green
console.log(restColors[1]);//blue
不定元素语法有助于从数组中提取特定元素并保证其余元素可用。其另一个应用为:复制数组。
//在ES5中实现数组复制,
var colors = ['red','green','blue'];
var cloneColor = colors.concat();
console.log(cloneColor);//['red','green','blue']
concat()方法设计初衷是连接两个数组,如果调用时不传参数就会返回当前数组的副本。
//ES6的实现
let colors = ['red','green','blue'];
let [...cloneColor] = colors;
console.log(cloneColor);//['red','green','blue']
注意:在被解构的数组中,不定元素必须为最后一个条目,在后面添加逗号会报错。
四、混合解构
混合使用对象解构和数组解构来创建更复杂的表达式。
let node = {
type:"identifier",
name:"foo",
loc:{
start:{
line:1,
column:1
},
end:{
line:2,
column:4
}
},
range:[0,3]
};
let {
loc:{start},
range:[startIndex]
} = node;
console.log(start.line);//1
console.log(start.column);//1
console.log(startIndex);//0
上面代码分别将node.loc.start和node.range[0]提取到变量start和startIndex中。
解构模式中loc: 和 range: 仅代表他们在node对象中所处的位置(也就是该对象的属性)。
五、解构参数
当定义一个接受大量可选参数的JS函数时,我们通常会创建一个可选对象,将额外的参数定义为这个对象的属性。
function setCookie(name,value,options) {
options = options || {};
let secure = options.secure,
path = options.path,
domain = options.domain,
expires = options.expires;
//do something
}
setCookie('type','js',{
secure:true,
expires:60000
})
示例中,name和value是必需参数,而secure、path、domain、expires是可选的。这么做是可以的,但是有个问题是:对于调用者来说,仅看函数声明的部分是无法辨识函数的预期参数的,必需通过阅读函数体才能确定所有参数的情况。
如果将options定义为解构参数,则可以清晰的了解函数预期传入的参数。如
function setCookie(name,value,{secure,path,domain,expires}) {
}
setCookie('type','js',{
secure:true,
expires:60000
})
对于使用者而言,解构参数变得更清晰了。
5.1 必须传值的解构参数
上述例子中,如果调用函数时不提供被解构的参数会导致程序抛出错误。setCookie('type','js')这么调用上述例子,程序会报错。因为缺失的第三个参数,其值为undefined。而解构参数只是将解构声明在函数参数的一个简写方法,当调用时,JS引擎其实做了这些事:
function setCookie(name,value,options) {
let {secure,path,domain,expires} = options;
}
如果解构表达式右侧的值为null或undefined,程序会报错。
如果解构参数是必需的,可以忽略这些问题。如果希望将解构参数定义为可选的,那么就必需为其提供默认值来解决这个问题:
function setCookie(name,value,{secure,path,domain,expires}={}) {
}
示例中,为解构参数添加了一个新对象作为默认值。调用时不传第三个参数也不会报错。
5.2 解构参数的默认值
可以为解构参数指定默认值,就像在解构赋值语句中做的那样,只需在参数后面添加等号并且指定一个默认值即可:
function setCookie(name,value,{
secure = false,
path = '/',
domain = 'example.com',
expires = new Date(Date.now()+360000)
}) {
}
在这段代码中,解构参数的每一个属性都有默认值,从而无须再逐一检查每一个属性是否都有默认值。然而,这种方法也有缺点:1、函数声明变得比以前复杂了 2、如果解构参数是可选的,那么仍然要给它添加一个空对象作为参数,否则会报错。所以建议对于对象类型的解构参数,为其赋予相同解构的默认参数:
function setCookie(name,value,{
secure = false,
path = '/',
domain = 'example.com',
expires = new Date(Date.now()+360000)
}= {
secure : false,
path : '/',
domain : 'example.com',
expires : new Date(Date.now() + 360000)
}) {
}
上面代码看起来有点冗余,可以将默认值提取到一个独立对象中,并且使用该对象作为解构和默认参数的一部分,从而消除这些冗余:
const setDefault = {
secure : false,
path : '/',
domain : 'example.com',
expires : new Date(Date.now() + 360000)
};
function setCookie(name,value,{
secure = false,
path = '/',
domain = 'example.com',
expires = new Date(Date.now()+360000)
}= setDefault) {
}
使用解构参数后,不得不面对处理默认参数的复杂逻辑,但它也好处的,如果要改变默认值,可以立即在setDefault中修改,改变的数据将自动同步到所有出现过的地方。

 

PS:本文摘自《深入理解ES6》一书。 

posted @ 2018-05-31 15:25  柒月未央  阅读(132)  评论(0编辑  收藏  举报