QCon全球软件开发大会9折优惠倒计时,了解详情 了解详情
写点什么

深入浅出 ES6(六):解构 Destructuring

2015 年 7 月 13 日

编者按:ECMAScript 6 已经正式发布了,作为它最重要的方言,Javascript 也即将迎来语法上的重大变革,InfoQ 特开设“深入浅出ES6 ”专栏,来看一下ES6 将给我们带来哪些新内容。本专栏文章来自 Mozilla Web 开发者博客,由作者授权翻译并发布。

译者按:Firefox 开发者工具工程师 Nick Fitzgerald 最初在他的博客上发布了一篇文章——《 ES6 中的解构赋值》,本文基于这篇博文做了些许补充。

什么是解构赋值?

解构赋值允许你使用类似数组或对象字面量的语法将数组和对象的属性赋给各种变量。这种赋值语法极度简洁,同时还比传统的属性访问方法更为清晰。

通常来说,你很可能这样访问数组中的前三个元素:

复制代码
var first = someArray[0];
var second = someArray[1];
var third = someArray[2];

如果使用解构赋值的特性,将会使等效的代码变得更加简洁并且可读性更高:

复制代码
var [first, second, third] = someArray;

SpiderMonkey(Firefox 的 JavaScript 引擎)已经支持解构的大部分功能,但是仍不健全。你可以通过 bug 694100 跟踪解构和其它 ES6 特性在 SpiderMonkey 中的支持情况。

数组与迭代器的解构

以上是数组解构赋值的一个简单示例,其语法的一般形式为:

复制代码
[ variable1, variable2, ..., variableN ] = array;

这将为 variable1 到 variableN 的变量赋予数组中相应元素项的值。如果你想在赋值的同时声明变量,可在赋值语句前加入varletconst关键字,例如:

复制代码
var [ variable1, variable2, ..., variableN ] = array;
let [ variable1, variable2, ..., variableN ] = array;
const [ variable1, variable2, ..., variableN ] = array;

事实上,用变量来描述并不恰当,因为你可以对任意深度的嵌套数组进行解构:

复制代码
var [foo, [[bar], baz]] = [1, [[2], 3]];
console.log(foo);
// 1
console.log(bar);
// 2
console.log(baz);
// 3

此外,你可以在对应位留空来跳过被解构数组中的某些元素:

复制代码
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

请注意,当你解构对象并赋值给变量时,如果你已经声明或不打算声明这些变量(亦即赋值语句前没有letconstvar关键字),你应该注意这样一个潜在的语法错误:

复制代码
{ blowUp } = { blowUp: 10 };
// Syntax error 语法错误

为什么会出错?这是因为 JavaScript 语法通知解析引擎将任何以{开始的语句解析为一个块语句(例如,{console}是一个合法块语句)。解决方案是将整个表达式用一对小括号包裹:

复制代码
({ safe } = {});
// No errors 没有语法错误

解构值不是对象、数组或迭代器

当你尝试解构nullundefined时,你会得到一个类型错误:

复制代码
var {blowUp} = null;
// TypeError: null has no properties(null 没有属性)

然而,你可以解构其它原始类型,例如:布尔值数值字符串,但是你将得到undefined

复制代码
var {wtf} = NaN;
console.log(wtf);
// undefined

你可能对此感到意外,但经过进一步审查你就会发现,原因其实非常简单。当使用对象赋值模式时,被解构的值需要被强制转换为对象。大多数类型都可以被转换为对象,但 nullundefined却无法进行转换。当使用数组赋值模式时,被解构的值一定要包含一个迭代器

默认值

当你要解构的属性未定义时你可以提供一个默认值:

复制代码
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 目前只实现了这个特性的前两种情况,第三种尚未实现。详情查看 bug 932080 。)

解构的实际应用

函数参数定义

作 为开发者,我们需要实现设计良好的 API,通常的做法是为函数为函数设计一个对象作为参数,然后将不同的实际参数作为对象属性,以避免让 API 使用者记住 多个参数的使用顺序。我们可以使用解构特性来避免这种问题,当我们想要引用它的其中一个属性时,大可不必反复使用这种单一参数对象。

复制代码
function removeBreakpoint({ url, line, column }) {
// ...
}

这是一段来自 Firefox 开发工具 JavaScript 调试器(同样使用 JavaScript 实现——没错,就是这样!)的代码片段,它看起来非常简洁,我们会发现这种代码模式特别讨喜。

配置对象参数

延伸一下之前的示例,我们同样可以给需要解构的对象属性赋予默认值。当我们构造一个提供配置的对象,并且需要这个对象的属性携带默认值时,解构特性就派上用场了。举个例子,jQuery 的ajax函数使用一个配置对象作为它的第二参数,我们可以这样重写函数定义:

复制代码
jQuery.ajax = function (url, {
async = true,
beforeSend = noop,
cache = true,
complete = noop,
crossDomain = false,
global = true,
// ... 更多配置
}) {
// ... do stuff
};

如此一来,我们可以避免对配置对象的每个属性都重复var foo = config.foo || theDefaultFoo;这样的操作。

(编者按:不幸的是,对象的默认值简写语法仍未在 Firefox 中实现,我知道,上一个编者按后的几个段落讲解的就是这个特性。点击 bug 932080 查看最新详情。)

与 ES6 迭代器协议协同使用

ECMAScript 6 中定义了一个迭代器协议,我们在《深入浅出ES6(二):迭代器和for-of 循环》中已经详细解析过。当你迭代 Maps (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) {
// ...
}

多重返回值

JavaScript 语言中尚未整合多重返回值的特性,但是无须多此一举,因为你自己就可以返回一个数组并将结果解构:

复制代码
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;

或者使用 CPS 变换:

复制代码
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 将 ES6 代码转译为相应的 ES5 代码。


再次感谢 Nick Fitzgerald 撰写 ES6 的解构特性。

下一次,我们将讲解一个新特性,函数一直以来都是 JavaScript 语言的基础构建单元,如果我们用稍短的方式实现函数是否令你感到激动?我自信地预测你 的答案一定是 yes!不过别轻易相信我说的话,发布下一篇文章时记得亲自来看看,我们一起深入浅出 ES6 箭头函数(Arrow Function)。

2015 年 7 月 13 日 01:2334132
用户头像

发布了 63 篇内容, 共 126.6 次阅读, 收获喜欢 35 次。

关注

评论

发布
暂无评论
发现更多内容

DMD钻石币质押软件系统开发内容

开發I852946OIIO

【源码系列】Spring 过滤器和拦截器

Alex🐒

spring 源码 Spring Framework

在线图片转base64工具

入门小站

工具

【LeetCode】传递信息Java题解

HQ数字卡

算法 LeetCode 7月日更

Python 没有函数重载?如何用装饰器实现函数重载?

华为云开发者社区

Python 装饰器 命名空间 函数 函数重载

Linux之head命令

入门小站

Linux

13万张表+数亿行代码,迁移只需数小时,还是异构数据库

华为云开发者社区

数据库迁移 DRS 华为云数据库 异构数据库 华为云UGO

开源商业化:满足各方底层需求

茶陵後

开源 开源社区 开源文化

视频 QoE 的平衡之道—揭秘网易云信 NERTC 视频质量控制系统

网易云信

视频 Qoe

Pano Flutter SDK 设计经验与实践浅谈

拍乐云Pano

【带你手撸Spring】没有哪个框架开发,能离开 Spring 的 FactoryBean!

小傅哥

spring 小傅哥 代理对象 FactoryBean Bean作用域

Rust从0到1-函数式编程-性能比较

rust 函数式编程 Performance 性能比较

华为云原生媒体网络,升级传统,赋能未来

华为云开发者社区

云原生 直播 TechWave 媒体网络 云视频

并发王者课-铂金05:致胜良器-无处不在的“阻塞队列”究竟是何面目

技术八点半

Java 多线程 并发 并发王者课

视赏家短视频系统软件开发详情

开發I852946OIIO

vue keep-alive(2):剖析keep-alive的实现原理—学习笔记整理

zhoulujun

Vue vue源码解读 keep-alive实现原理

广州高薪招聘捐卵女孩

Geek_831110

环球旅游积分GTC系统开发内容

开發I852946OIIO

聊聊知乎订单系统迁移

知一

监控 软件开发 系统架构 重构 订单系统

如何做好IT项目管理?

万事ONES

IT 项目经理 项目管理工具

如何实施 SCRUM ?

万事ONES

项目管理 Scrum 敏捷开发 看板 ONES

区块链:从根儿上解决2%的人拥有80%的财富全球社会问题

CECBC区块链专委会

解析对偶理论与对偶单纯性法

华为云开发者社区

模型 对偶理论 对偶单纯性法 对偶 线性规划

吃瓜->某飞学城搞爬虫抬高踩低夜幕组织??

百里丶落云

爬虫 夜幕

“区块链贸易融资生态”应用案例发布

CECBC区块链专委会

vue keep-alive(1):vue router如何保证页面回退页面不刷新?

zhoulujun

Vue vue-router keep-alive 页面缓存

SQL 居然还能在 Apache ShardingSphere 上实现这些功能?

SphereEx

【Flutter 专题】129 图解 ToggleButtons 按钮切换容器组

阿策小和尚

Flutter 小菜 0 基础学习 Flutter Android 小菜鸟 7月日更

Camtasia实用技巧之视频剪辑

淋雨

视频剪辑 Camtasia 录屏软件

DGTT矿机软件开发|DGTT矿机系统APP开发

开發I852946OIIO

ASL公链软件开发|ASL公链系统APP开发

开發I852946OIIO

数据库运维技术发展与展望

数据库运维技术发展与展望

深入浅出ES6(六):解构 Destructuring-InfoQ