为何使用解构功能
在ECMAScript 5及早期版本中,开发者们为了从对象和数组中获取特定数据并赋值给变量,编写了许多看起来同质化的代码,就像这样:
let options = {repeat: true,save: false};let repeat = options.repeat,save = options.save;
如果options有几千条属性要赋值给变量 则要写几千条options.变量名 十分不便
这段代码从options对象中提取了repeat和save的值并将其存储为同名局部变量,提取的过程极为相似,想象一下,如果你要提取更多变量,则必须依次编写类似的代码来为变量赋值,如果其中还包含嵌套结构,只靠遍历是找不到真实信息的,必须要深入挖掘整个数据结构才能找到所需数据。所以ECMAScript 6为对象和数组都添加了解构功能,将数据结构打散的过程变得更加简单,可以从打散后更小的部分中获取所需信息。许多语言都通过极少量的语法实现了解构功能,以简化获取信息的过程;而ECMAScript 6中的实现实际上利用了你早已熟悉的语法:对象和数组字面量的语法。
对象解构
对象解构的语法形式是再一个赋值操作符左边放置一个对象字面量
左边放一个对象字面量 右边放对应要赋值的对象名 左边对象字面量中变量名 对应右边对象中的属性名
let node = {
type: "Identifier",
name: "foo"
};
let { type, name } = node;
console.log(type); //"Identifier"
console.log(name); //"foo"
node.type的值被存储在名为type的变量中;node.name的值被存储在名为name的变量中
不要忘记初始化程序
**
如果使用var、let或const解构声明变量,则必须要提供初始化程序(也就是等号右侧的值)。下面这几行代码全部会导致程序抛出语法错误,它们都缺少了初始化程序:
var { type, name }; //报错
let { type, name }; //报错
const { type, name };// 报错
解构赋值
在给变量赋值是使用解构语法。你可能在定义变量之后想要修改它们的值,例如在定义变量之后想要修改它们的值。(二次赋值)
let node = {
type: "Identifier",
name: "foo"
},
type = "Literal",
name = 5;
// 使用解构鱍为多个变量赋值
({ type, name } = node);
console.log(type); //"Identifier"
console.log(name); //"foo"
在这个示例中,声明变量type和name时初始化了一个值,在后面几行中,通过解构赋值的方法,从node对象读取相应的值重新为这两个变量赋值。请注意,一定要用一对小括号包裹解构赋值语句,JavaScript引擎将一对开放的花括号视为一个代码块,而语法规定,代码块语句不允许出现在赋值语句左侧,添加小括号后可以将块语句转化为一个表达式,从而实现整个解构赋值的过程。
解构赋值表达式的值与表达式右侧(也就是=右侧)的值相等,如此一来,在任何可以使用值的地方你都可以使用解构赋值表达式。想象一下给函数传递参数值的过程
let node = {
type: "Identifier",
name: "foo"
},
type = "Literal",
name = 5;
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的属性的行为都会触发运行时错误。
默认值
使用解构赋值值表达式时,如果指定的局部变量名称在对象中不存在,这个局部变量会被赋值为undefined
let node = {
type: "Identifier",
name: "foo"
};
let { type, name, value } = node;
console.log(type); //"Identifier"
console.log(name); //"foo"
console.log(value);//undefined
当指定的属性不存在时,可以随意定义一个默认值,在属性名称后添加一个等号(=)和相应的默认值即可
let node = {
type: "Identifier",
name: "foo"
};
let { type, name, value = true } = node;
console.log(type); //"Identifier"
console.log(name); //"foo"
console.log(value);//true
为变量value设置了默认值true,只有当node上没有该属性或者该属性值为undefined时该值才生效 此处没有node.value属性,所以value使用了预设的默认值
为非同名局部变量
使用不同命名的局部变量来存储对象属性的值
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 = "bar" } = node;
console.log(localType);// Identifier
console.log(localName);// bar
由于node.name属性不存在,变量被默认赋值为”bar”。
嵌套对象解构
let node = {
type: "Identifier",
name: "foo",
loc: {
start: {
line: 1,
column: 1
},
end: {
line: 1,
column: 4
}
}
};
// 提取node.loc.start
let { loc: { start } } = node;
console.log(start.line);
console.log(start.column);
在这个示例中,我们在解构模式中使用了花括号,其含义为在找到node对象中的loc属性后,应当深入一层继续查找start属性。在上面的解构示例中,所有冒号前的标识符都代表在对象中的检索位置,其右侧为被赋值的变量名;如果冒号后是花括号,则意味着要赋予的最终值嵌套在对象内部更深的层级中。
let node = {
type: "Identifier",
name: "foo",
loc: {
start: {
line: 1,
column: 1
},
end: {
line: 1,
column: 4
}
}
};
//提取node.loc.start 给 localStart
let { loc: { start: localStart } } = node;
console.log(localStart.line); //1
console.log(localStart.column); //1
数组解构
let colors = ["red", "green", "blue"];
let [firstColor, secondColor] = colors;
console.log(firstColor); //"red"
console.log(secondColor); // "green"
指定数组中需取的值
let colors = ["red", "green", "blue"];
let [, , thirdColor] = colors;
console.log(thirdColor); //blue
注 当通过var、let或const声明数组解构的绑定时,必须要提供一个初始化程序,这一条规定与对象解构的规定类似。
解构赋值
let colors = ["red", "green", "blue"];
firstColor = "black",
secondColor = "purple;
[firstColor, secondColor] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"
交换两个变量的值
es5**
let a = 1,
b = 2,
tmp;
tmp = a;
a = b;
b = tmp;
console.log(a); //2
console.log(b); //1
es6
let a = 1,
b = 2;
[a, b] = [b, a];
console.log(a)//2;
console.log(b);//1;
默认值
也可以在数组解构赋值表达式中为数组中的任意位置添加默认值,当指定位置的属性不存在或其值为undefined时使用默认值:
let colors = ["red"];
let [firstColor, secondColor = "green"] = colors;
console.log(firstColor); // red
console.log(secondColor); // green
嵌套数组解构
嵌套数组解构与嵌套对象解构的语法类似,在原有的数组模式中插入另一个数组模式,即可将解构过程深入到下一个层级:
let colors = ["red", ["green", "lightgreen"], "blue"];
let [firstColor, secondColor] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // ["green", "lightgreen"]
[firstColor, [secondColor]] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"
不定元素
在数组解构语法中有一个相似的概念:不定元素。在数组中,可以通过…语法将数组中的其余元素赋值给一个特定的变量
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 color = ["red", "green", "blue"];
var clonedColors = colors.concat();
console.log(clonedColors); //["red", "green", "blue"]
es6
var color = ["red", "green", "blue"];
var [...clonedColors] = colors;
console.log(clonedColors); //["red", "green", "blue"]
混合解构
let node = {
type: "Identifier",
name: "foo",
loc: {
start: {
line: 1,
column: 1
},
end: {
line: 1,
column: 4
}
},
range: [0, 3]
};
// 提取node.loc.start node.loc.range
let { loc: { start }, range: [startIndex, startIndex2] } = node;
console.log(start.line); //1
console.log(start.column); //1
console.log(startIndex); //0
console.log(startIndex2); //3
解构参数
解构可以用在函数参数的传递过程中,这种使用方式更特别。当定义一个接受大量可选参数的JavaScript函数时,我们通常会创建一个可选对象,将额外的参数定义为这个对象的属性:
function setCookie(name, value, options) {
options = options || {};
let secure = options.secure,
path = options.path,
domain = options.domain,
expires = options.expires;
// 设置cookie的代码;
}
setCookie("type", "js", {
secure: true,
export: 6000
})
现在的问题是,仅查看函数的声明部分,无法辨识函数的预期参数,必须通过阅读函数体才可以确定所有参数的情况
重写
function setCookie(name, value, { secure, path, domain, expires }) {
// 设置cookie的代码
}
setCookie("type", "js", {
secure: true,
export: 6000
})
必须传值的解构参数
解构参数有一个奇怪的地方,默认情况下,如果调用函数时不提供被解构的参数会导致程序抛出错误。举个例子,调用上一个示例中的setCookit()函数,如果不传递第3个参数,会报错:
// 程序报错!
setCookie("type","js");
缺失的第3个参数,其值为undefined,而解构参数只是将解构声明应用在函数参数的一个简写方法,其会导致程序抛出错误。当调用setCookie()函数时,JavaScript引擎实际上做了这些事情
function setCookie(name, value, options) {
let { secure, path, domain, expires } = options;
// 设置cookie的代码
}
如果解构赋值表达式的右值为null或undefined,则程序会报错,同理,若调用setCookie()函数时不传入第3个参数,等同于传undefined
解决方案:提供默认值
function setCookie(name, value, { secure, path, domain, expires } = {}) {
// 设置cookie的代码
}
这个示例中为解构参数添加了一个新对象作为默认值,secure、path、domain及expires这些变量的值全部为undefined,这样即使在调用setCookie()时未传递第3个参数,程序也不会报错
**
解构参数的默认值
可以为解构参数指定默认值,就像在解构赋值语句中做的那样,只需在参数后添加等号并且指定一个默认值即可:
function setCookie(name, value,
{
secure = false,
path = "/",
domain = "example.com",
expires = new Date(Date.now() + 360000000)
}
) {
}
在这段代码中,解构参数的每一个属性都有默认值,从而无须再逐一检查每一个属性是否都有默认值。然而,这种方法也有很多缺点:首先,函数声明变得比以前复杂了;其次,如果解构参数是可选的,那么仍然要给它添加一个空对象作为参数,否则像setCookie(”type”,”js”)这样的调用会导致程序抛出错误。这里建议对于对象类型的解构参数,为其赋予相同解构的默认参数:
function setCookie(name, value,
{
secure = false,
path = "/",
domain = "example.com",
expires = new Date(Date.now() + 360000000)
} = {
secure = false,
path = "/",
domain = "example.com",
expires = new Date(Date.now() + 360000000)
}
) {
}
现在函数变得更加完整了,第一个对象字面量是解构参数,第二个为默认值。但是这会造成非常多的代码冗余,你可以将默认值提取到一个独立对象中,并且使用该对象作为解构和默认参数的一部分,从而消除这些冗余:
**
const setCookieDefaults = {
secure = false,
path = "/",
domain = "example.com",
expires = new Date(Date.now() + 360000000)
}
function setCookie(name, value,
{
secure = setCookieDefaults.secure,
path = setCookieDefaults.path,
domain = setCookieDefaults.domain,
expires = setCookieDefaults.expires
} = setCookieDefaults
) {
}
在这段代码中,默认值已经被放到setCookieDefaults对象中,除了作为默认参数值外,在解构参数中可以直接使用这个对象来为每一个绑定设置默认参数。使用解构参数后,不得不面对处理默认参数的复杂逻辑,但它也有好的一面,如果要改变默认值,可以立即在setCookieDefaults中修改,改变的数据将自动同步到所有出现过的地方。
**
