ES6 In Depth 是一个系列,介绍 ECMAScript 标准第六版(简称 ES6)中添加到 JavaScript 编程语言的新功能。
编者注:今天文章的早期版本由 Firefox 开发者工具工程师 Nick Fitzgerald 撰写,最初发表在 Nick 的博客上,名为 ES6 中的解构赋值。
什么是解构赋值?
解构赋值允许您使用类似于数组或对象字面量的语法将数组或对象的属性分配给变量。这种语法可以非常简洁,同时比传统的属性访问更清晰。
在没有解构赋值的情况下,您可以像这样访问数组中的前三个项
var first = someArray[0];
var second = someArray[1];
var third = someArray[2];
使用解构赋值,等效代码变得更加简洁和可读
var [first, second, third] = someArray;
SpiderMonkey(Firefox 的 JavaScript 引擎)已经支持大多数解构赋值,但并非全部。在错误 694100 中跟踪 SpiderMonkey 的解构赋值(以及一般的 ES6)支持。
解构数组和可迭代对象
我们已经在上面的数组上看到了一个解构赋值的例子。语法的通用形式是
[ variable1, variable2, ..., variableN ] = array;
这将把 variable1 到 variableN 分配给数组中的对应项。如果想要在同一时间声明变量,可以在赋值之前添加 var
、let
或 const
var [ variable1, variable2, ..., variableN ] = array;
let [ variable1, variable2, ..., variableN ] = array;
const [ variable1, variable2, ..., variableN ] = array;
事实上,variable
是一个误称,因为您可以根据需要嵌套模式
var [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo);
// 1
console.log(bar);
// 2
console.log(baz);
// 3
</pre>
Furthermore, you can skip over items in the array being destructured:
<pre>var [,,third] = ["foo", "bar", "baz"];
console.log(third);
// "baz"
您可以使用“剩余”模式捕获数组中的所有尾部项
var [head, ...tail] = [1, 2, 3, 4];
console.log(tail);
// [2, 3, 4]
当访问数组中超出范围或不存在的项时,您会得到与索引相同的結果:undefined
。
console.log([][0]);
// undefined
var [missing] = [];
console.log(missing);
// undefined
请注意,带有数组赋值模式的解构赋值也适用于任何可迭代对象
function* fibs() {
var a = 0;
var b = 1;
while (true) {
yield a;
[a, b] = [b, a + b];
}
}
var [first, second, third, fourth, fifth, sixth] = fibs();
console.log(sixth);
// 5
解构对象
对象上的解构允许您将变量绑定到对象的不同的属性。您指定要绑定的属性,然后指定将该属性的值绑定到的变量。
var robotA = { name: "Bender" };
var robotB = { name: "Flexo" };
var { name: nameA } = robotA;
var { name: nameB } = robotB;
console.log(nameA);
// "Bender"
console.log(nameB);
// "Flexo"
当属性和变量名称相同时,有一个有用的语法捷径
var { foo, bar } = { foo: "lorem", bar: "ipsum" };
console.log(foo);
// "lorem"
console.log(bar);
// "ipsum"
就像数组上的解构一样,您可以进一步嵌套和组合解构
var complicatedObj = {
arrayProp: [
"Zapp",
{ second: "Brannigan" }
]
};
var { arrayProp: [first, { second }] } = complicatedObj;
console.log(first);
// "Zapp"
console.log(second);
// "Brannigan"
当您对未定义的属性进行解构时,您将获得 undefined
var { missing } = {};
console.log(missing);
// undefined
您需要注意的一个潜在问题是,当您使用对象上的解构来分配变量,而不是声明它们时(当没有 let
、const
或 var
时)
{ blowUp } = { blowUp: 10 };
// Syntax error
发生这种情况是因为 JavaScript 语法告诉引擎将以 {
开头的任何语句解析为块语句(例如,{ console }
是一个有效的块语句)。解决方法是将整个表达式括在括号中
({ safe } = {});
// No errors
解构不是对象、数组或可迭代对象的的值
当您尝试对 null
或 undefined
使用解构时,您会得到一个类型错误
var {blowUp} = null;
// TypeError: null has no properties
但是,您可以对其他原始类型(如布尔值、数字和字符串)进行解构,并获得 undefined
var {wtf} = NaN;
console.log(wtf);
// undefined
这可能出乎意料,但经过进一步检查,原因很简单。当使用对象赋值模式时,要解构的值必须能够转换为 Object
。大多数类型可以转换为对象,但 null
和 undefined
无法转换。当使用数组赋值模式时,该值必须具有迭代器。
默认值
您还可以为要解构的属性未定义的情况提供默认值
var [missing = true] = [];
console.log(missing);
// true
var { message: msg = "Something went wrong" } = {};
console.log(msg);
// "Something went wrong"
var { x = 3 } = {};
console.log(x);
// 3
(编者注:此功能目前仅在 Firefox 中针对前两种情况实现,不包括第三种情况。请参阅错误 932080。)
解构的实际应用
函数参数定义
作为开发人员,我们通常可以通过接受具有多个属性的单个对象作为参数来公开更符合人体工程学的 API,而不是强迫我们的 API 使用者记住许多单个参数的顺序。我们可以使用解构来避免在想要引用某个属性时重复此单个参数对象
function removeBreakpoint({ url, line, column }) {
// ...
}
这是来自 Firefox DevTools JavaScript 调试器(也是用 JavaScript 实现的——yo dawg)的真实代码片段。我们发现这种模式特别令人满意。
配置对象参数
扩展前面的例子,我们还可以为要解构的对象的属性提供默认值。当我们有一个旨在提供配置并且许多对象属性已经具有合理的默认值的对象时,这特别有用。例如,jQuery 的 ajax
函数以配置对象作为第二个参数,可以像这样重写
jQuery.ajax = function (url, {
async = true,
beforeSend = noop,
cache = true,
complete = noop,
crossDomain = false,
global = true,
// ... more config
}) {
// ... do stuff
};
这避免了为配置对象的每个属性重复 var foo = config.foo || theDefaultFoo;
。
(编者注:不幸的是,对象简写语法中的默认值在 Firefox 中仍然没有实现。我知道,自上次说明以来,我们已经有了好几个段落来进行处理。请参阅错误 932080,了解最新更新。)
使用 ES6 迭代协议
ECMAScript 6 还定义了迭代协议,我们在本系列的早期文章中讨论过。当您迭代Map
(ES6 添加到标准库中的内容)时,您会得到一系列 [key, value]
对。我们可以解构这对来轻松访问键和值
var map = new Map();
map.set(window, "the global");
map.set(document, "the document");
for (var [key, value] of map) {
console.log(key + " is " + value);
}
// "[object Window] is the global"
// "[object HTMLDocument] is the document"
仅迭代键
for (var [key] of map) {
// ...
}
或仅迭代值
for (var [,value] of map) {
// ...
}
多个返回值
尽管多个返回值没有在语言本身中内置,但它们并不需要,因为您可以返回一个数组并解构结果
function returnMultipleValues() {
return [1, 2];
}
var [foo, bar] = returnMultipleValues();
或者,您可以使用对象作为容器并命名返回的值
function returnMultipleValues() {
return {
foo: 1,
bar: 2
};
}
var { foo, bar } = returnMultipleValues();
这两种模式最终都比保留临时容器要好得多
function returnMultipleValues() {
return {
foo: 1,
bar: 2
};
}
var temp = returnMultipleValues();
var foo = temp.foo;
var bar = temp.bar;
或使用延续传递风格
function returnMultipleValues(k) {
k(1, 2);
}
returnMultipleValues((foo, bar) => ...);
从 CommonJS 模块导入名称
还没有使用 ES6 模块?还在使用 CommonJS 模块?没问题!当导入一些 CommonJS 模块 X 时,模块 X 通常会导出比您实际想要使用的更多函数。使用解构,您可以明确指定要使用给定模块的哪些部分,并避免弄乱命名空间
const { SourceMapConsumer, SourceNode } = require("source-map");
(如果您确实使用 ES6 模块,您知道类似的语法在 import
声明中可用。)
结论
因此,如您所见,解构在许多独立的小案例中很有用。在 Mozilla,我们对此有很多经验。Lars Hansen 十年前在 Opera 中引入了 JS 解构赋值,Brendan Eich 稍后将支持添加到 Firefox。它在 Firefox 2 中发布。因此,我们知道解构会偷偷地进入您对语言的日常使用,在各个地方静静地使您的代码更短、更简洁。
五周前,我们说 ES6 会改变您编写 JavaScript 的方式。我们特别注意的是这种功能:可以逐个学习的简单改进。总而言之,它们最终会影响您参与的每个项目。通过演化来实现革命。
更新解构赋值以符合 ES6 是一项团队合作。特别感谢 Tooru Fujisawa(arai)和 Arpad Borsos(Swatinem)为其做出的杰出贡献。
Chrome 正在开发对解构赋值的支持,其他浏览器无疑也会及时添加支持。目前,如果您想在 Web 上使用解构赋值,您需要使用Babel 或Traceur。
再次感谢 Nick Fitzgerald 撰写本周的文章。
下周,我们将介绍一个功能,它不过是一种更简短的编写 JS 已经具备的功能的方式——一种一直是语言基础构建块的功能。您会关心吗?稍微简短一点的语法是您能兴奋的事情吗?我自信地预测答案是肯定的,但不要相信我的话。下周加入我们,我们深入了解 ES6 箭头函数。
Jason Orendorff
ES6 深入浅出 编辑器
关于 Nick Fitzgerald
我喜欢计算、自行车、嘻哈、书籍和绘图仪。我的代词是 he/him。
12 条评论