本文摘自《深入理解JavaScript特性》,本书将JavaScript新特性融入简单易懂的示例中,包括ES6及后续更新,助你大幅提升代码表达能力。
JavaScript之父Brendan Eich作序推荐。
JavaScript 已经从 1995 年的一个为了赢得战略优势的市场营销策略,变成了如今(2017 年)世界上使用最广泛的应用运行平台中的核心编程语言。该语言不再只是在浏览器中运行,现在也用于创建桌面和移动应用,还用于硬件设备,甚至是 NASA 的太空服设计。
JavaScript 是如何做到这一步的,接下来它又会怎么做呢?
1.1 JavaScript标准简史
1995 年,NetScape 公司想要构建一个动态的网页,但 HTML 无法实现这一点。为此,他们雇用 Brendan Eich 专门为浏览器开发一门功能类似于 Scheme 的语言。Brendan 加入之后,得知上级主管希望这门语言的语法像 Java,而且这一决定已经开始实施。
Brendan 花 10 天写出了 JavaScript 的第一个原型,主要实现了 Scheme 的一类函数和 Self 的原型等构造。这个初始版 JavaScript 的代号为 Mocha。它没有数组,没有对象字面量,任何错误都会给出警告。此外,它也没有异常处理,这也是如今仍有很多操作返回 NaN
或 undefined
的原因。Brendan 对 DOM0 级和 JavaScript 第一个版本的实现成为了这门语言标准化的基础。
1995 年 9 月,Netscape Navigator 2.0 beta 版发布,JavaScript 的修订版也内置其中,对外宣传时名叫 LiveScript。同年 12 月,Navigator 2.0 beta 3 发布,此时 LiveScript 又重新命名为 JavaScript(后成为 Sun 的注册商标,Sun 现在是 Oracle 旗下公司)。这次发布后不久,Netscape 公司推出了服务器端 JavaScript 的实现,用于在 Netscape Enterprise Server 上运行脚本,并将其命名为 LiveWire。① 1996 年,微软在 IE3 中推出 JScript,即通过逆向工程实现的 JavaScript。JScript 在服务器端也可以在互联网信息服务器(IIS,Internet information server)中运行。
①1998 年的这个小册子(docs.oracle.com/cd/E19957-0…)详细介绍了服务器端 JavaScript 以及 LiveWire 的方方面面。
1996 年,ECMA 的一个技术委员会 TC39 将 JavaScript 以名称 ECMAScript(ES)标准化为 ECMA-262 规范。为什么叫 ECMAScript 呢?因为 Sun 不同意将 JavaScript 商标转让给 ECMA,虽然微软提议叫 JScript,但其他成员公司又不想用,结果就只能使用 ECMAScript 这个尴尬的名字。
当时,TC39 开会主要就是争论该采用 Netscape 的 JavaScript,还是微软的 JScript。尽管如此,该委员会还是取得了成果,他们坚定不移地支持向后兼容,推动引入了严格相等运算符(===
和 !==
),不会影响依赖松散相等比较算法的现有程序。
ECMA-262 的第一版于 1997 年 6 月发布。次年 6 月,国际标准化组织对这个规范进行了完善和认真审查,并以 ISO/IEC 16262 的形式发布,这就是它的第二版。
1999 年 12 月发布的第三版标准化了正则表达式、switch
语句、do
/whille
、try
/catch
、Object#hasOwnProperty
,以及其他一些特性。其中大部分特性已经可以在 Netscape 的 JavaScript 运行环境 SpiderMonkey 中使用。
之后不久,TC39 发布了 ES4 规范的草案。ES4 的早期工作直接导致了 2000 年年中 JScript.NET 的产生 ②,并最终促进 2006 年 Flash 中 ActionScript 3 的诞生。③
②可以在微软网站上找到最初的公告(2000 年 7 月)。
③Brendan Eich 在播客 JavaScript Jabber 中介绍了很多关于 JavaScript 起源的故事。
此时,关于 JavaScript 应该朝哪个方向发展的不同意见导致了规范制定工作停滞不前。对于 Web 标准的发展而言,这时的情形很微妙:微软几乎垄断了 Web 行业,却对制定标准毫无兴趣。
2003 年,AOL 裁掉了 50 名 Netscape 员工 ④,Mozilla 基金会随之成立。同时,由于微软占据了超过 95% 的 Web 浏览器市场份额,TC39 被迫解散。
④2003 年 7 月的 The Mac Observer 有该新闻的相关报道。
直到两年后,Brendan 在 Mozilla 以 Firefox 日益增长的市场份额为杠杆使得微软回归,ECMA 才得以重新开始 TC39 的相关工作。2005 年年中,TC39 再次开始召开定期会议。对于 ES4,计划引入模块系统、类、迭代器、生成器、解构、类型注释、尾调用优化、代数类型以及其他各类功能。这次的新增内容过于庞大,导致 ES4 一次又一次地延期了。
2007 年,TC39 分裂为两派:一派主张推出 ES3.1,即只在 ES3 的基础上完善改进;另一派则主张推出 ES4,新特性繁多,且亟待规范化。直到 2008 年 8 月 ⑤,两派才就 ES3.1 达成一致,ES3.1 后来又发展为 ES5。ES4 的提议似乎废弃了,但实际上其中很多特性最终写进了 ES6(达成和解时美其名曰 Harmony,即“和谐”),当然,其中一些特性还在讨论,另外少数特性确实已经废弃、被拒或撤销。ES3.1 也为 ES4 的逐步实现奠定了基础。
⑤2008 年,Brendan Eich 给 es-discuss 发了一封邮件,其中介绍了当时的情况,此时距 ES3 发布都快 10 年了。
2009 年 12 月,ES3 发布 10 周年,ECMAScript 第五版发布。这一版汇集了当时浏览器中业已存在的扩展,或者说“事实标准”,增加了 get
和 set
存取器,增强了 Array
原型的函数特性,还引入了反射和内省机制,以及对 JSON 解析和严格模式的支持。
2011 年 6 月,经过再次审查和编辑,该规范形成了第三版的国际标准 ISO/IEC 16262:2011,并作为 ECMAScript 5.1 发布。
2015 年 6 月,TC39 又花 4 年完成了 ECMAScript 6。第六版是该语言面世以来改动最大的一个版本,实现了许多 ES4 中被推迟到 Harmony 中的提案。本书主要探讨的就是 ES6。
在 ES6 的制定过程中,另一个旨在推动 Web 发展的组织 WHATWG(网页超文本应用技术工作小组)于 2012 年推出了一个文档,以记录 ES5.1 和浏览器实现在兼容性和可操作性方面的差异。这个工作小组标准化了之前规范中没有提及的 String#substr
,统一了在 HTML 标签中包装字符串的几种方法,明确写出了 Object.prototype
中的 __proto__
和 __defineGeter__
等原型属性,同时还做出了其他一些改进。⑥ 这些工作最终形成了一份独立的 Web ECMAScript 规范,最终于 2015 年被加入到附录 B 中。附录 B 是 ECMAScript 核心规范中的参考部分,这意味着浏览器可以不遵照其进行实现。此次更新之后,附录 B 也成为了 Web 浏览器的规范和要求。
⑥要想了解将 Web ECMAScript 规范合并到主干时所做的全部更改,请参见 WHATWG 博客。
第六版是 JavaScript 历史上一个意义重大的里程碑。除了众多新功能之外,ES6 也是 ECMAScript 成为持续迭代标准的一个转折点。
1.2 持续迭代的ECMAScript
ES3 在 10 年内未有重大改变,之后 4 年才推出 ES6。这一漫长历程清楚地表明 TC39 流程需要改进。之前的修订流程是最终发布日期驱动的。只要有议题未能达成一致,下次修订就会变得遥遥无期。时间一长又会有新的特性提出,进而导致更多拖延。小的修订总会因大的新增而拖延,而大的新增迫于最终发布日期的压力,就会仓促通过修订,以免造成一拖再拖。
ES6 发布后,TC39 优化了提案修订的流程 ⑦,以满足现代预期:迭代具有经常性和一贯性,且规范的制定要更加民主化。基于这一点,TC39 不再采用古老的 Word 文档形式,而是使用 Ecmarkup(用于编写 ECMAScript 规范的 HTML 语言的超集)和 GitHub 来提交需求,大大增加了非成员的提案数量 ⑧,他们就像是外在的参与者一样。这种新形式是持续的,并且更加透明:最新的规范草案随时可以查看,而无须像之前那样,必须从网页下载 Word 文档或 PDF 版本。
⑦2013 年 9 月的 PPT,Post-ES6 Spec Process,介绍了优化后的提案修订流程。
⑧TC39 采纳的提案参见 mjavascript.com/out/tc39-pr…。
Firefox、Chrome、Edge、Safari 以及 Node.js 对 ES6 规范的支持均已超过 95%⑨。现在我们就可以在这些浏览器中使用已经支持的功能,而不用等到它们对 ES6 的支持度达到 100%。
⑨ES6 的浏览器兼容性报告参见 kangax.github.io/compat-tabl…。
新的流程引入了 4 个不同的成熟度阶段。⑩ 提案越成熟,最终添加到规范中的可能性越大。
⑩TC39 的提案流程文档参见 tc39.github.io/process-doc…。
只要还未作为正式提案提交,关于修改或新增内容的任何讨论、想法或者提议都会被视为有前途的“稻草人”提案(阶段 0),但只有 TC39 委员会的成员才可以创建“稻草人”提案。在编写本书时,有 10 多个活跃的“稻草人”提案。⑪
⑪“稻草人”提案参见 github.com/tc39/propos…。
阶段 1 表示提案被正式提出,希望能够解决各方关注的问题,厘清与其他提案的交集,并实现问题。这一阶段的提案需要明确描述一个具体的问题,并给出该问题的具体解决方案。阶段 1 的提案通常包括以下几方面的内容:高级 API 描述、演示性的用法示例、内部语义和算法的讨论。阶段 1 的提案可能会随着流程的推进而发生很大的改变。
阶段 2 的提案是规范的初步草案。从这一步开始,需要在运行环境中验证具体的实现。实现的方式可以是腻子脚本(polyfill)、能让运行环境支持提案的用户代码、原生支持提案内容的引擎实现,也可以是使用构建工具将源代码转换、编译成现有引擎可以执行的代码。
阶段 3 的提案是候选推荐提案。只有规范的编辑和指定的审查人员在最终的规范上签字确认,提案才能进入阶段 3。另外,还需要实现者表示出对该提案感兴趣。实际上,只有满足以下 3 个条件之一,提案才能进入阶段 3:某个浏览器已经实现该提案,有高度吻合的腻子脚本,有类似 Babel 的实时编译工具的支持。除了修复使用过程中新发现的问题,阶段 3 的提案不会再有其他改动。
要想进入阶段 4,提案必须有两个独立的实现方案通过验收测试。进入阶段 4 的提案最终会添加到 ECMAScript 的下一版中。
从现在开始,ECMAScript 预计每年都会发布新版本。为了与年度发版计划统一,规范的版本从现在开始与出版的年份相关联。因此,ES6 也就是 ES2015,之后将有 ES2016(而不是 ES7)、ES2017,等等。实际上,ES2015 这个称呼并没有被接受,大家还是习惯称其为 ES6。ES2016 也是在命名约定改变前就发布了的,因此有时人们也称其为 ES7。由于 ES6 这一名称已经为社区普遍接受,我们抛开不谈。最终的规范版本将是 ES6、ES2016、ES2017、ES2018,以此类推。
调整后的提案流程加上每年都发布一版的强制约定形成了更加一致的发布过程。这也意味着规范的修订版本号变得不再那么重要。现在的重点是提案的不同阶段,我们相信将来大家会越来越少提及 ECMAScript 标准的某个特定修订版。
1.3 浏览器支持和辅助工具
如果能够有两个 JavaScript 引擎提供独立的实现,阶段 3 的候选推荐提案最有可能进入下一个版本的规范之中。实际上,只要阶段 3 的提案有实验性的引擎实现或者腻子脚本,再或者可以通过编译器得到支持,那么就已经能够安全地在实际开发中使用了。其实,阶段 2 和更早阶段的提案也有为 JavaScript 开发人员所使用的,这也加强了实现者和使用者之间的反馈循环。
Babel 等将代码作为输入并生成 Web 平台原生支持的输出(HTML、CSS 或 JavaScript)的编译器通常称为转译器(transpiler),它是编译器的一个子集。如果我们想要在代码中使用某个 JavaScript 引擎还没有普遍实现的提案,Babel 之类的编译器可以帮我们将相应的代码转换成现有 JavaScript 实现可以运行的代码。
代码转换是在构建时完成的,因此用户拿到的是自己的 JavaScript 运行环境所支持的代码。这一机制降低了对运行环境的要求,也让 JavaScript 开发者能够更早地使用新的语言功能和语法。对于规范的编写者和实现者来说,这也是非常有利的,因为这样他们就可以得到可行性、迫切性、可能存在的 bug,以及边界用例等方面的反馈。
转译器可以将 ES6 代码转换成浏览器普遍可以解释的 ES5 代码。这是如今在生产环境中运行 ES6 代码的最可靠方式:通过构建生成 ES5 代码,这样新旧浏览器都可以执行。
这种方式同样适用于 ES7 及后续版本。由于语言规范每年都会发布新版本,我们可以期待编译器支持 ES2017 输入、ES2018 输入,等等。同样,随着浏览器的支持度越来越好,编译器可以逐渐降低支持 ES6 输出、ES7 输出的复杂性。从这种意义上说,我们可以将 JavaScript 转译器看作移动的窗口,其输入是使用最新语法编写的代码,输出是不影响浏览器运行的最新代码。
接下来我们探讨如何在工作中使用 Babel。
1.3.1 Babel转译器简介
Babel 可以将 ES6 代码编译成 ES5 代码。生成的 ES5 代码很容易看懂,因此非常适合还不完全熟悉新特性的人来理解新特性。
Babel 的在线读取 - 求值 - 打印循环(REPL,read-evaluate-print loop)转换器是学习 ES6 的好帮手,无须安装 Node.js、babel
CLI,也无须手工编译源码。
REPL 提供了一个源码输入框,用于自动实时编译,编译后的代码位于源码右侧。
我们可以在 REPL 中写几行代码,如下所示。
var double = value => value * 2
console.log(double(3))
// <- 6复制代码
我们可以在右侧看到转换后的 ES5 等价代码,如图 1-1 所示。更新源码时,转译结果也会实时更新。
图 1-1:在线 Babel REPL 是交互式学习 ES6 的好工具
Babel REPL 也是用于试验本书中介绍的一些特性的有效工具。但需要知道的是,Babel 并不转换新的内置对象,如 Symbol
、Proxy
和 WeakMap
。这些引用会原封不动地保留下来,最终由执行 Babel 输出代码的运行环境提供这些内置对象。如果想要支持还没有实现这些内置对象的运行环境,可以在代码中引入 babel-polyfill
包。
在一些较老的 JavaScript 版本中,想要语义正确地实现某些特性是很难的,甚至完全不可能。此时可以用腻子脚本弥补这个问题,但腻子脚本通常不能覆盖所有情况,因此需要做一些妥协。所以,在将转译后的使用了内置对象和腻子脚本的代码发布到生产环境之前,最好多做一些测试。
考虑到这种情况,最好还是等浏览器整体支持这些新的内置对象时再使用它们。我们建议你使用不依赖于这些新内置对象的替代方案。与此同时,学习这些特性也很重要,这样我们对 JavaScript 语言的理解才不会落后。
Chrome、Firefox 和 Edge 等现代浏览器现在已经支持 ES2015 及后续版本的大部分内容,所以在支持的前提下,可以通过它们的开发者工具来尝试新特性。产品级应用需要依赖新 JavaScript 特性时,还是推荐使用转换器进行编译处理,这样应用能够支持更多的 JavaScript 运行环境。
除了 REPL,Babel 还提供了一个在命令行中运行的 Node.js 包,可以通过 Node.js 的包管理工具 npm
来安装它。
下载 Node.js。安装
node
后,就可以在终端内使用npm
命令了。
开始之前,我们先创建一个项目目录以及一个用于描述 Node.js 应用的 package.json 文件。可以通过 npm
在命令行中创建 package.json 文件。
mkdir babel-setup
cd babel-setup
npm init --yes复制代码
执行
init
命令时传递--yes
参数表示不用询问我们,可以使用npm
提供的默认值来配置 package.json 文件。
再创建一个 example.js 文件,并在其中加入以下代码,然后将其保存到刚刚创建的 babel- setup 目录下的 src 子目录。
var double = value => value * 2
console.log(double(3))
// <- 6复制代码
在常用的终端中输入以下两行命令即可安装 Babel。
npm install babel-cli@6 --save-dev
npm install babel-preset-env@6 --save-dev复制代码
通过
npm
安装的包存放于项目根目录的 node_modules 目录下。可以通过创建npm script
命令或使用require
声明来访问这些包。
--save-dev
参数会将所安装的包作为开发依赖添加到 package.json 文件中。这样一来,当我们将项目移植到一个新环境时,只须运行npm install
命令就可以重新安装每个依赖。
@
符号指明了包的特定版本。使用@6
则是告诉npm
安装babel-cli
的6.x
版本中最新的一个。这种偏好限制可以确保我们的应用将来不出问题,因为它可以确保永远不会安装7.0.0
或后续版本,从而避免了7.0.0
之后的版本包含当前无法预见的破坏性变化。
接下来,我们修改 package.json 中的 scripts
属性的值,如下所示。babel-cli
提供的 babel
命令行工具可以获取 src 目录下的全部内容,将它们编译成目标输出格式,并将结果保存到 dist 目录中,并且保留文件的原始目录结构。
{
"scripts": {
"build": "babel src --out-dir dist"
}
}复制代码
加上前面安装的包,现在我们构成了以下这个超小的 package.json 文件。
{
"scripts": {
"build": "babel src --out-dir dist"
},
"devDependencies": {
"babel-cli": "^6.24.0",
"babel-preset-env": "^1.2.1"
}
}复制代码
scripts
对象中列举的所有命令都可以通过npm run
执行,这种执行方式会临时修改环境变量$PATH
的值,这样我们才可以在系统未全局安装babel-cli
的情况下找到babel-cli
并在命令行执行。
如果现在在终端内执行 npm run build
,你就能看到生成后的 dist/example.js 文件。输出的文件和源文件完全相同。这是因为 Babel 还不知道编译的目标格式,我们需要先配置它。在 package.json 旁创建一个 .babelrc 文件,并在其中写入以下 JSON。
{
"presets": ["env"]
}复制代码
这里的 env
就是前面通过 npm
安装的 babel-preset-env
,它给 Babel 添加了一系列插件,以便将不同的 ES6 代码转换为 ES5。这个预设包含很多插件,其中就有插件将 example.js 中的箭头函数转换成 ES5 代码。env
预设会根据最新浏览器已经支持的特性启用相关的转换插件。这个预设是可配置的,我们可以决定向后兼容到哪个浏览器。兼容的浏览器越多,编译生成的包就越大;兼容的浏览器越少,能满足的用户就越少。当然,到底什么配置最合适,最终还是要经过研究才能决定。默认配置是启用所有转换插件,兼容尽可能多的运行环境。
再运行一次编译脚本就能够看到输出的 ES5 代码了。
» npm run build
» cat dist/example.js
"use strict"
var double = function double(value) {
return value * 2
}
console.log(double(3))
// <- 6复制代码
接下来我们再介绍一个代码检查工具 eslint
,它可以帮助我们确保项目代码的质量。
1.3.2 使用ESLint提高代码质量和一致性
开发项目时,我们会发现冗余或无用的代码,编写很多新代码,删除无关或没必要的功能,也会为适应新架构而来回搬运代码。随着代码越写越多,团队规模也会随之变化。刚开始可能只有几个人,甚至只有一个人,但随着项目规模越来越大,参与编码的人也会越来越多。
代码检查工具可以帮助我们发现语法错误。现代检查工具通常都是可定制的,允许我们建立适合自己团队的代码约定。坚守一致的代码风格和质量基准可以使团队的编码风格趋于一致。不同的团队成员可能会对代码风格有不同的意见,但有了代码检查工具和协商一致的配置后,这些意见就变成了明确可遵循的规则。
首先是确保程序能被解析,其次可能要防止 throw
抛出字符串字面量作为异常,或者不允许在生产环境中使用 console.log
和 debugger
语句。然而,要求所有函数调用都只能有一个参数就有点过了。
虽然代码检查工具能够定义并强制推行一种编码风格,但定义规则时也不能太任性。如果规则太严格,可能会影响开发效率,得不偿失。反之,规则太宽松的话,代码就不能保持统一风格。
要想把握好这个度,就应该尽量不使用多数情况下都不能改善程序的规则。新增一条规则时,应当扪心自问,添加它能否显著改善现有代码库以及未来的新代码?
ESLint 是一个现代代码检查工具,它集合了一些插件,具有不同的规则,支持自定义。我们可以决定不遵守规则时是输出警告还是导致停机错误。与安装 babel
一样,我们也可以通过 npm
安装 eslint
。
npm install eslint@3 --save-dev复制代码
接下来我们需要配置 ESLint。因为本地安装了 eslint
,所以可以在 node_modules/.bin 中找到它的命令行工具。执行以下命令能够引导我们配置 ESlint。首先,告诉它我们想使用一套流行的风格,然后选择 Standard⑫,最后选择 JSON 格式的配置文件。
⑫注意,Standard 是一种自我宣告,并未由任何官方组织进行实际的标准化。其实,只要能够保持统一,使用哪种风格并不重要。在阅读项目代码时,一致性有助于减少困扰。Airbnb 风格指南也是很受欢迎的,与 Standard 不同,它默认不可省略分号。
./node_modules/.bin/eslint --init
? How would you like to configure ESLint?
Use a popular style guide
? Which style guide do you want to follow? Standard
? What format do you want your config file to be in? JSON复制代码
除了个别规则,eslint
还支持扩展预定义规则,这些规则以 Node.js 包的形式存在。在多项目甚至社区中共享配置时,这会很方便。选择 Standard 后,可以看到 ESLint 向 package.json 中添加了一些依赖,即包含预设 Standard 规则的包,同时创建了一个名为 .eslintrc.json 的文件,其中包含以下内容。
{
"extends": "standard",
"plugins": [
"standard",
"promise"
]
}复制代码
直接引用包含 npm
实现细节的 node_modules/.bin 目录并不好。虽然前面初始化 ESLint 配置时这么引用了,但其实应该避免这样做,而且检查代码时也不应该每次都那么输入一遍。为此,我们在 packge.json 中添加一个脚本命令。
{
"scripts": {
"lint": "eslint ."
}
}复制代码
前面安装 Babel 时提到过,npm run
在执行脚本命令时会将 node_modules 加入 PATH
环境变量。这样一来,在检查代码时,只要执行 npm run lint
,npm
就会在 node_modules 目录中找到 ESLint CLI。
我们来看看以下的示例文件 example.js,其中的代码故意没有遵守规则,以演示 ESLint 具体做了什么。
var goodbye='Goodbye!'
function hello(){ return goodbye}
复制代码
if(false){}复制代码
执行 lint
脚本命令时,ESLint 会标识文件中所有错误的地方,如图 1-2 所示。
图 1-2:ESLint 可以帮助我们检查语法错误,保持代码风格统一
如果在执行命令时传递 --fix
参数,那么 ESLint 能够自动修复大多数的风格问题。在 package.json 中加入以下脚本命令。
{
"scripts": {
"lint-fix": "eslint . --fix"
}
}复制代码
此时执行 lint-fix
只会看到两个错误:未使用 hello
以及 false
是一个不变的条件。其他错误都已经修复了,结果如以下代码所示。上述两个错误没有修复,因为 ESLint 会保持中立,不对代码语义做预设推断,以免导致语义改变。这样一来,--fix
就能帮助我们有效解决编码风格问题,同时又不会破坏逻辑。
var goodbye = 'Goodbye!'
function hello() { return goodbye }
复制代码
if (false) {}复制代码
prettier
也是一种代码检查工具,可用于自动格式化代码。我们可以配置prettier
自动重写代码,以确保代码遵循设定的首选项,如使用给定的空格缩进,统一使用单引号或双引号,末尾自动加逗号,或者限制最大行长度。
现在我们知道了如何将现代 Javascript 代码编译成每个浏览器都能理解的代码,以及如何正确检查和格式化代码。接下来概述一下 ES6 的特性,并展望一下 JavaScript 的未来。
1.4 ES6特性
ES6 规范足足有 566 页,是 ES5.1 规范 258 页的两倍多。ES6 的主要变化可以归纳为以下几类:
- 语法糖
- 新机制
- 更好的语义
- 更多的内置对象和方法
- 对原有限制的非破坏性解决方案
语法糖是 ES6 中最重要的组成部分,包括用新类来表达对象继承、箭头函数以及属性值简写等一系列更简洁的语法。此外,解构、剩余参数以及扩展运算符等我们将介绍的特性也提供了更加语义化的编程方式。第 2 章和第 3 章将介绍 ES6 中的这部分内容。
ES6 提供了几种新机制来描述异步流程:代表一个操作最终结果的 Promise、代表一系列值的迭代器,以及能产生一系列值的特殊迭代器——生成器。基于这些新概念和结构,ES2017 提供了 async
/await
,让我们可以像编写同步代码那样编写异步代码。第 4 章将介绍这里提到的迭代及流控制机制。
在 JavaScript 中,使用任意字符串作为键的普通对象来创建映射是很常见的。如果键值来自用户输入,且没有验证,那么极有可能产生漏洞。为此,ES6 引入了一些新的原生内置对象来管理集合和映射,并且没有只能使用字符串作为键的限制。第 9 章将介绍相关内容。
代理对象用于重新定义可以通过 JavaScript 反射完成的操作。代理对象和其他语境中的代理类似,比如网络路由中所说的代理。它可以拦截 JavaScript 对象的任何交互,比如属性的定义、删除以及访问。鉴于代理的工作机制,我们很难用腻子脚本全面实现其功能:当然,腻子脚本是有的,只是在某些场景下其实现与规范不符。第 6 章将介绍代理。
除了新的内置对象,ES6 还对 Number
、Math
、Array
以及 String
对象进行了一定的扩展。第 7 章将介绍添加在这些内置对象上的一系列新实例方法和静态方法。
ES6 还为 JavaScript 引入了一个原生模块系统。第 8 章从 Node.js 使用的 CommonJS 模块系统讲起,然后详细讲解 JavaScript 原生模块的语义。
因为 ES6 引入了太多修改,所以很难做到将其新特性与现有 JavaScript 知识自然整合。为此,第 9 章将用一整章的篇幅来分析每个特性的优点和重要性,以便你对 ES6 建立起初步的概念,并基于此开始使用 ES6。
1.5 JavaScript的未来
JavaScript 语言已经从 1995 年一门没啥名气的语言发展为今天这样一门强大的语言。虽然 ES6 向前跨越了一大步,却远远未到终点。鉴于每年都会有新的规范发布,如何跟上新规范发布的步伐就很重要了。
了解 1.2 节中介绍的持续迭代流程后,我们知道,要想跟进标准,首先就要定期访问 TC39 提案库 ⑬。我们要时刻关注候选推荐提案(即阶段 3 的提案),因为这些提案最有可能加入新规范。
⑬TC39 收录的所有提案参见 mjavascript.com/out/tc39-pr…。
用一本书来介绍一门快速发展的语言是不太可能的。因此,关注 TC39 提案库、订阅周刊 ⑭、阅读 JavaScript 博客 ⑮ 是及时跟进 JavaScript 最新进展的有效方式。
⑭这种电子周刊很多,如 Pony Foo Weekly、Javascript Weekly。
⑮Pony Foo 网站上有很多关于 ECMAScript 开发的文章,Axel Rauschmayer 也写了很多关于这方面的文章。
撰写本书时,开发者期待已久的 Async 函数已经加入规范,并在 ES2017 中发布。此时此刻有很多候选提案,比如支持异步加载原生 JavaScript 模块的动态 import()
,以及使用 ES6 中针对参数列表和数组引入的剩余和扩展运算符来枚举对象属性。
虽然本书的主要关注点是 ES6,但我们同样会学习重要的候选推荐,如刚刚提及的 Async 函数、动态 import()
调用、对象剩余 / 扩展,以及其他内容。