在编码过程中,我们经常定义许多对象和数组,然后有组织地从中提取相关的信息片段。ES6
的解构特性,可以简化这项工作。解构是一种打破数据结构,将其拆分为更小部分的过程。
未使用解构的做法
let options = {
repeat: true,
save: false
};
// 从对象中提取数据
let repeat = options.repeat;
save = options.save;
这段代码从options
对象提取了repeat
和save
的值并将其存储为同名局部变量。如果要提取的变量更多,甚至还包含嵌套结构,靠遍历会够呛。
解构
ES6
解构的实现,实际上是利用了我们早已熟悉的对象和数字字面量的语法。
对象解构
let node = {
type: "Identifier",
name: "foo"
};
let { type, name } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
可见,只要在赋值操作的左边放置一个对象字面量就可以了。type
和name
都是局部声明的变量,也是用来从options
对象读取相应值的属性名称。
数组解构
let colors = [ "red", "green", "blue" ];
let [ firstColor, secondColor ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"
使用的是数组字面量。我们从colors
数组中解构出了”red”和”green”两个值,分别存储在变量firstColor
和secondColor
中。
数组解构模式中,也可以直接省略元素,只为感兴趣的元素提供变量:
let colors = [ "red", "green", "blue" ];
let [ , , thirdColor] = colors; // 不提供变量的,占位一下就可以
console.log(thirdColor); // "blue"
解构赋值
对象解构赋值
我们也可以对已声明的变量使用解构赋值:
let node = {
type: "Identifier",
name: "foo"
},
type = "Literal",
name = 5;
// 使用解构语法为变量赋值,要用小括号扩住
({ type, name } = node);
console.log(type); // "Identifier"
console.log(name); // "foo"
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"
consolo.log(name); // "foo"
数组解构赋值
数组解构也可以用于赋值上下文:
let colors = [ "red", "green", "blue"];
firstColor = "black";
secondColor = "purple";
[ firstColor, secondColor ] = colors;
console.log(firstColor); // "red"
console.log(secondClor); // "green"
数组解构赋值可以很方便地交换两个变量的值。
先看看ES5
,我们交换两个变量的做法:
let a = 1,
b = 2,
tmp;
tmp = a;
a = b;
b = tmp;
console.log(a); // 2
console.log(b); // 1
使用数组解构赋值:
let a = 1,
b = 2;
// 左侧是解构模式,右侧是为交换过程创建的临时数字字面量
[ a, b ] = [ b, a ];
console.log(a); // 2
console.log(b); // 1
解构赋默认值
对象解构赋默认值:
let node = {
type: "Identifier",
name: "foo"
};
let { type, name, value1, value2 = true } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
console.log(value1); // undefined 在解构对象上没有对应的值
console.log(value2); // true
数组解构赋默认值:
let colors = [ "red" ];
let [ firstColor, secondColor, thirdColor = "blue"] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // undefined
console.log(thirdColor); // "blue"
为非同名变量赋值
let node = {
type: "Identifier",
name: "foo"
};
let { type: localType, name: localName = "bar" } = node;
console.log(localType); // "Identifier"
console.log(localName ); // "bar"
type: localType
语法的含义是读取名为type
的属性并将其值存储在变量localType
中。
嵌套解构
对象嵌套解构:
let node = {
type: "Identifier",
name: "foo",
loc: {
start: {
line: 1,
colomn: 1
},
end: {
line: 1,
column: 4
}
}
};
let { loc: { start }} = node;
console.log(start.line); // 1
console.log(start.column); // 1
上面的解构示例中,冒号前的标识符代表在对象中的检索位置,其右侧为被赋值的变量名;如果冒号后面是花括号,则意味着要赋予的最终值嵌套在对象内部更深的层级中。
也可以使用与对象名不同的局部变量:
...
let { loc: { start: localStart }} = node;
console.log(localStart.line); // 1
console.log(localStart.column); // 1
数组嵌套解构
let colors = [ "red", [ "green", "lightgreen" ], "blue" ];
let [ firstColor, [ secondColor ] ] = colors;
console.log(firstColor); // "red"
console.log(secondClor); // "green"
不定元素
ES6
的函数引入了不定参数,而在数组解构语法中有个相似的概念:不定元素。
在数组中,可以通过…语法将数组中剩余的元素赋值给一个指定的变量:
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" ];
// concat()方法的设计初衷是连接两个数组,不传参数会返回当前数组的副本
var clonedColors = colors.concat();
console.log(clonedColors); // "[red,green,blue]"
// ES6通过不定元素来实现相同功能
let colors = [ "red", "green", "blue" ];
let [ ...clonedColors ] = colors;
console.log(clonedColors); // "[red,green,blue]"
不定元素必须为最后一个条目,在后面继续添加逗号会导致程序抛出语法错误。
对象和数组混合解构
let node = {
type: "Identifier",
name: "foo",
loc: {
start: {
line: 1,
colomn: 1
},
end: {
line: 1,
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
这种方法极为有效,尤其是当你从JSON
配置中提取信息时,不再需要遍历整个结构了。
请记住,解构模式中loc:和range:仅代表它们在node
对象中所处的位置(也就是该对象的属性)。
解构参数
当定义一个接受大量可选参数的JavaScript
函数时,我们通常会创建一个可选对象:
// options的属性表示其他参数
function setCookie(name, value, options) {
options = options || {};
let secure = options.secure,
path = options.path,
domain = options.domain,
expires = options.expires;
// 设置cookie的代码
}
// 第三个参数映射到options中
setCookie("type", "js", {
secure: true,
expires: 60000
});
许多JavaScript
库中都有类似的setCookie()函数,而在示例函数中,name
和value
是必需参数,而secure
、path
、domain
和expires
则不然,这些参数相对而言没有优先级顺序,将它们列为额外的命名参数也不合适,此时为options
对象设置同名的命名属性是一个很好的选择。现在的问题是,仅查看函数的声明部分,无法辨别函数的预期参数,必须通过阅读函数体才可以确定所有参数的情况。
如果将options
定义为解构参数,则可以更清晰地了解函数预期传入的参数:
function setCookie(name, value, { secure, path, domain, expires }) {
// 设置cookie的代码
}
setCookie("type", "js", {
secure: true,
expires: 60000
});
对于调用setCookie()函数的使用者而言,解构参数变得更清晰了。
必须传值的解构参数
如果调用函数时不提供被解构的参数会导致程序抛出错误:
// 程序报错
setCookie("type", "js");
缺失的第三个参数,其值为undefined。而解构参数只是将解构声明应用在函数参数的一个简写方法,会导致程序抛出错误。当调用setCookie()函数时,JavaScript引擎实际上做了下面的事情:
function setCookie(name, value, options) {
let { secure, path, domain, expires } = options;
// 设置cookie的代码
}
如果解构赋值表达式右值为null
或undefined
,则程序会报错。因此若调用setCookie()函数时不传入第3个参数,程序会报错。
如果解构参数是必需的,大可忽略掉这个问题;但如果希望将解构参数定义为可选的,那就必须为其提供默认值来解决这个问题:
function setCookie(name, value, { secure, path, domain, expires } = {}) {
// ...
}
示例中为解构参数添加一个新的对象作为默认值,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
中修改,改变的数据将自动同步到所有出现过的地方。