每个 JavaScript 开发者都该了解的 ES2018 新特性

avatar
花呗借呗前端团队 @蚂蚁集团

原文作者:Faraz Kelhini

译者:UC 国际研发 Jothy


写在最前:欢迎你来到“UC国际技术”公众号,我们将为大家提供与客户端、服务端、算法、测试、数据、前端等相关的高质量技术文章,不限于原创与翻译。

编者按:曾几何时,年少的我捧着阮一峰老师的《ES6 标准入门》,感叹 JS 变迁实在太快,好怕学不动了。直至写了几年 ES6 的今日,回头看方知:不要为了学 ES X 而学 ES X,无论 ES 几其实都是语法糖,是辅助角色,重点是想清楚它能为我们的开发带来什么好处,而不是本末倒置。今天介绍的 ES2018 新特性还是有蛮多亮点的,一起来看看吧。


ECMAScript 标准的第九版,官宣为 ECMAScript 2018(或简称 ES2018),已于 2018 年 6 月发布。从 ES2016 开始,每隔一年就会发布一版 ECMAScript 规范的新版本,并添加不多于主版本的功能。 最新的这个版本延续了每年发布的周期,新增了四个新的 RegExp 特性,rest/spread 属性,异步迭代和 Promise.prototype.finally。 此外,它还从标记模板中删除了转义序列的语法限制。

我们将在后面的小节中详细解释这些新变化 😉。



rest/spread 属性

回顾 ES2015,最有趣的功能当数 spread 运算符。 该运算符极大简化了数组的复制及合并。你可以使用它替换concat()slice()方法:


在必须将数组的各个项分别作为参数传入函数的情况下,扩展运算符也派得上用场。 例如:


通过向对象文法添加 spread 属性,ES2018 进一步扩展了此语法。 使用 spread 属性,你可以将对象自身的可枚举属性复制到新对象上。举个例子 🌰:

在此代码中,... 运算符用于检索 obj1 的属性并将它们分配给 obj2。 在 ES2018 之前,这么做会报错的。 如果出现多个属性同名的情况,将会取最后一个值:


Spread 属性还提供了一种合并两个或多个对象的新方法,可以替代 Object.assign() 方法使用:

在此代码中,Object.assign() 方法会执行其继承的 setter 属性,而 spread 属性则完全忽略了这一步。

切记!spread 属性只复制可枚举属性。 在下面的例子中,type 属性不会出现在复制出的对象中,因为其enumerable 属性为 false

继承属性即使是可枚举的,也会被忽略:

在这段代码中,car2 继承了carcolor 属性。 由于 spread 仅复制对象自身的属性,因此返回值中不包含 color 属性。

请记住,spread 只是对象的浅复制。 如果属性中包含对象,则仅复制对象的引用:

copy1 中的 x 属性与copy2中的x 属性引用了内存中同一个对象,因此严格等于(strict equality)运算符返回 true.

ES2015 新增的另一有用功能是 rest 参数,它使 JavaScript程序员能够使用...将值表示为数组。 例如:

arr 的第一项被赋值给 x,剩下的被赋值给 rest 变量。 这种名为数组解构的模式非常受欢迎,以至于 Ecma 技术委员会(Ecma Technical Committee)决定为对象带来类似的功能:

此代码使用 rest 属性解构赋值,将对象 obj 剩余的自身可枚举属性复制到新对象rest中。 需要引起注意的是,rest 属性必须始终位于对象的末尾,否则会报错:

此外,在对象中使用多个 rest 语法也会报错,除非它们是嵌套使用的:


Rest/Spread 属性支持

Node.js:
  • 8.0.0(需要 --harmony 运行时 flag)

  • 8.3.0(完全支持)


异步迭代

迭代数据集是编程的重要组成部分。 在 ES2015 之前,JavaScript提供了for,for...inwhile 等语句,以及map(),filter()forEach() 等方法。 为了方便程序员一个个地处理集合元素,ES2015 引入了迭代器接口。

如果对象具有 Symbol.iterator 属性,则表示它是可迭代的。 在 ES2015 中,字符串和集合对象(如Set, MapArray)带有Symbol.iterator 属性,因此是可迭代的。 以下代码说明了如何每次访问一个可迭代元素☝️:

Symbol.iterator 是个广为人知的符号,用于表示返回迭代器的函数。与迭代器交互主要使用next()方法。此方法返回一个具有valuedone 两个属性的对象。 value 属性包含集合中下一个元素的值。done 属性包含truefalse,表明是否已到达集合的末尾。

默认情况下普通对象不可迭代,但如果在其上定义了 Symbol.iterator 属性,则它可变为可迭代对象,如下所示:

collection 对象是可迭代的,因为它定义了 Symbol.iterator 属性。 iterator 使用 Object.keys() 方法获取对象属性名的数组,然后将其赋值给常量values.它还定义了一个计数器变量i,初始值为 0. 当执行迭代器时,它返回一个包含 next() 方法的对象。 每次调用 next() 方法时,它都返回一个 {value, done} 键值对,其中 value 保存集合中的下一个元素,done 保存一个布尔值,表示迭代器是否已达到集合的末尾。

虽然以上代码运行完美,但它本无需如此复杂。 所幸,生成器( generator )函数可以大大简化该过程:

在此生成器中,for...in 循环用于枚举集合,yield 每个属性的值。 结果与前一个示例完全相同,但代码量大大减少。

迭代器的缺点是它们不适合表示异步数据源。 ES2018 的补救方案是异步迭代器(asynchronous iterators)和异步可迭代对象(asynchronous iterables)。 异步迭代器与传统迭代器的不同点在于,它不返回 {value,done} 的形式的普通对象,而是返回一个完成(fulfill) {value,done}promise.异步可迭代对象定义了一个返回异步迭代器的 Symbol.asyncIterator 方法(注意不是 Symbol.iterator)。

举个例子🌰可能更清楚些:

请注意,使用 promises 的迭代器不可能达到相同的结果。 虽然普通的同步迭代器可以异步产生确定值,但它仍然需要同步确定“完成(done)”的状态。


同样,你可以使用生成器函数简化此过程,如下所示:

通常生成器函数会返回带有next()方法的生成器对象。 当调用 next() 时,它返回一个 {value,done} 键值对,其 value 属性保存了yield 的值。 异步生成器与之类似,只不过它返回的是一个完成了 {value,done} 的promise.

使用 for...of 语句可以轻松迭代可迭代对象,但是 for...of 不能与异步可迭代对象一起使用,因为 valuedone 不是同步产生的。 出于这个原因,ES2018 提供了 for...await...of 语句。 我们来看一个例子:

在此代码中, for...await...of 语句隐式调用集合对象上的Symbol.asyncIterator方法以获取异步迭代器。 每次循环时,都会调用迭代器的 next() 方法,该方法返回一个 promise. 一旦 promise 完成,就会将结果对象的 value 属性读取到x变量。 循环继续,直到返回对象的 done 属性值为true.

敲黑板!for...await...of 语句仅在异步生成器和异步函数中有效。 违反此规则会报 SyntaxError(语法错误)。


next() 方法可能会返回 rejected promise. 为了优雅地处理被 reject 的 promise,你可以使用try...catch语句包裹for...await...of 语句,如下所示:

异步迭代器支持

Node.js:

  • 8.10.0(需要 --harmony_async_iteration flag)

  • 10.0.0(完全支持)


Promise.prototype.finally

ES2018另一振奋人心的特效是finally()方法。 之前有几个 JavaScript 库实现了类似的方法,并且它被证实是有用的。这促使 Ecma技术委员会正式将finally()添加到规范中。 使用该方法,开发者可无需理会 promise 命数如何,直接执行这个代码块中的代码。 我们来看一个简单的例子:

finally() 方法可在操作完成后进行一些扫尾(clean up)工作,无论操作是否成功。 在此代码中,finally() 方法在数据获取处理后直接隐藏了加载 spinner。 无论 promise 完成与否,函数中的注册代码都会执行,开发者不必在 then()catch() 方法中重复编写逻辑。

使用promise.then(func, func)也可实现与promise.then(func, func) 同样的效果,但你必须在 fulfillment 句柄及 rejection 句柄中重复相同的代码,或者引入一个变量:


then()catch() 相同,finally() 方法总是返回一个 promise,因此你可以链接更多的方法。 一般来说,我们会将 finally() 作为最后一环。但某些情况,例如在创建 HTTP 请求时,在 finally() 之后链接另一个catch(),以处理请求中可能发生的错误是不错的实践。

Promise.prototype.finally 支持

Node.js:

10.0.0(完全支持)


RegExp 新特性

ES2018 为 RegExp 对象增加了四个新特性,进一步提高了 JavaScript 的字符串处理能力。 这些特性如下:

  • s(dotAll)标志

  • 可命名捕获组

  • Lookbehind断言

  • Unicode 属性转义

s (dotAll) Flag

点(.)是正则表达式模式中的特殊字符,它匹配除换行符之外的任何字符,例如换行符(\n)或回车符(\r)。要匹配包括换行符在内的所有字符,解决方法是使用两个相反短字的字符类,例如[\d\D]. 此字符类告诉正则表达式引擎找到一个数字(\d)或非数字(\D)的字符。 因此,它匹配任意字符:

ES2018 引入了一种模式,其中点可用于实现相同的结果。可以使用s标志在每个正则表达式的基础上激活此模式:

利用标志来选择性使用新特性的好处是向后兼容,保证使用点字符的现有正则表达式模式不受影响。

可命名捕获组

在一些正则表达式模式中,使用数字来引用捕获组可能会造成混淆。 例如,采用正则表达式 /(\d{4})-(\d{2})-(\d{2})/ 匹配日期。 由于美式英语中的日期符号与英式英语不同,因此很难知道哪个组指的是日,哪个组指的是月:

ES2018 引入了使用(?<name>...)语法的命名捕获组。 因此,匹配日期的模式可以用不太模糊的方式编写:

你可以使用 \k<name> 语法在模式中再次调用命名捕获组。 例如,要查找句子中连续的重复单词,可以使用 /\b(?<dup>\w+)\s+\k<dup>\b/:

要将命名捕获组用于 replace() 方法的替换字符串,你可以使用 $<name> 构造。 例如:

后行断言

ES2018 为 JavaScript 带来了后行断言(lookbehind assertion),该断言已在其他语言的正则表达式使用多年。 以前,JavaScript 只支持先行断言(lookahead assertion)。 后行断言用 (?<=...) 表示,使你能够根据模式之前的子字符串匹配模式。 例如,如果你想要在不捕获货币符号的情况下以美元,英镑或欧元匹配产品的价格,你可以使用/(?<=\$|£|€)\d+(\.\d*)?/:

还有一个负向的后行断言,用 (?<!...) 表示。 负向后行断言允许你匹配不跟在某后行断言之后的模式(译者注:差点把我自己都绕晕了😷 举个简单的例子(?<!a)b: 断言 b 前面没有 a,匹配 bb 但不匹配 ab,最终捕获 b)。 例如,模式 /(?<!un)available/ 可在无 “un” 前缀的情况下匹配 available:

Unicode 属性转义

ES2018 提供了一种称为 Unicode 属性转义的新转义序列类型,它在正则表达式中提供对完整 Unicode 的支持。 假设你要匹配字符串中的 Unicode 字符 ㉛. 虽然我们认为 ㉛ 是一个数字,但是我们不能用 \d 匹配它,因为它只支持 ASCII [0-9] 字符。此外,Unicode 属性转义也可用于匹配 Unicode 中的任何十进制数:

同样,如果要匹配任意 Unicode 单词(划掉)字母字符,可以使用 \p{Alphabetic}:

还有一个否定版本的\p{...},用\P{...},表示:

除了字母和数字之外,还有几个属性可以在 Unicode 属性转义中使用。 你可以在当前规范提案中找到支持的 Unicode 属性列表。

地址:https://tc39.github.io/proposal-regexp-unicode-property-escapes/#sec-static-semantics-unicodematchproperty-p

RegExp 新特性支持

Node.js:

  • 8.3.0(需要 --harmony 运行时 flag)

  • 8.10.0(支持 s(dotAll) 标志和后行断言)

  • 10.0.0(完全支持)


模板字符串修订

当模板字符串紧跟在表达式之后时,它会被称为标记模板字符串。 当你想要使用函数解析模板字符串时,标记模板会派上用场。 看看这个例子:

上面的代码调用了标记表达式(它是常规函数)并传递模板字符串。 该函数只是修改字符串的动态部分并返回它。

在 ES2018 之前,标记的模板字符串具有与转义序列相关的语法限制。 反斜杠后跟某些字符序列被视为特殊字符:\x 被解析为十六进制转义符,\u 被解析为unicode转义符,\_ 后跟一个数字被解析为八进制转义符。 因此,解释器将诸如 "C:\xxx\uuu""\ubuntu" 之类的字符串视为无效的转义序列,并将抛出SyntaxError.

ES2018 从标记模板中删除了这些限制,它会将无效转义序列表示为 undefined,而不是抛出错误:

请记住,在常规模板字符串中使用非法转义序列仍会报错:

模板字符串修订支持

Node.js:

  • 8.3.0 (需要 --harmony 运行时 flag)

  • 8.10.0(完全支持)


总结

我们已经仔细研究了 ES2018 中引入的几个关键特性,包括异步迭代,rest/spread 属性,Promise.prototype.finally以及 RegExp对象的新增特性。 虽然有些浏览器厂商尚未完全实现其中一些功能,但由于有 Babel 这样的 JavaScript 转换器,我们仍可以在今天使用它们。

ECMAScript 正在迅速发展,并且每隔一段时间就会引入新功能,欢迎查看完整提案列表👏,了解全部新功能。 有啥功能让你特别兴奋吗?快快和我分享叭~

提案地址:https://github.com/tc39/proposals/blob/master/finished-proposals.md


原文地址:https://css-tricks.com/new-es2018-features-every-javascript-developer-should-know/


好文推荐:

一起来燃烧 Bundle 的“卡路里”



“UC国际技术”致力于与你共享高质量的技术文章

欢迎关注我们的公众号、将文章分享给你的好友