以下是 ECMAScript 2016, 2017, 和 2018 中新增功能的示例
要跟上 JavaScript(ECMAScript) 更新的脚步很难,更难的是找到有用的代码示例。
因此,在本文中,我将介绍在 ES2016,ES2017 和 ES2018(最终草案)中添加的TC39已完成提案中列出的所有 18 个新特性,并一一展示有用的示例。
这篇文章很长,不过应该很容易读,可以当成一篇“Netflix binge reading”。我保证你读完后将对这些新特性有很多的了解。
好,我们一个一个来看看。
ECMAScript 2016
includes 是 Array 的一个实例方法,用来轻松查找某个项是否在 Array 中(包括 NaN,这点 indexOf 做不到)
1 | const arr = [ 1, 2, 3, 4, NaN]; |
冷知识:JavaScript 规范制定者本想给它取名叫
contains,但这显然已被 Mootools 使用,因此他们使用了includes。
数学运算如加法和减法分别有 + 和 - 等运算符。与他们类似,** 运算符通常用于幂运算。在 ECMAScript 2016 中引入了 **,取代 Math.pow。
1 | // 不使用 |
ECMAScript 2017
Object.value() 是一个新的函数,它与 Object.keys() 类似,但返回的事 Object 所有属性的值(不包括来自原型链的属性)。
1 | const cars = { 'BMW': 3, 'Tesla': 2, 'Toyota': 1 }; |
Object.entries() 与 Object.keys 相关,但不是仅返回 keys,而是以数组方式返回 keys 和 values。这让你可以很容易地遍历对象,或者把对象转换为 Maps。
例 1:
1 | const cars = { 'BMW': 3, 'Tesla': 2, 'Toyota': 1 }; |
例 2:
1 | const cars = { 'BMW': 3, 'Tesla': 2, 'Toyota': 1 }; |
String 增加了两个实例方法 – String.prototype.padStart 和 String.prototype.padEnd,用于在原字符串的开头或结尾添加一段字符串或空字符串。
1 | 'someString'.padStart(numberOfCharcters [,stringForPadding]); |
这在需要对齐事物的场景下很方便,诸如漂亮的打印显示或终端打印。
在下面的例子中,我们有一组长度不同的数字。为了显示好看,我们希望在它们前面补“0”,让它们长度都达到 10。我们可以使用 padStart(10, '0') 轻松实现这一点。
1 | // ES2017 |
我们打印多个不同长度的项目并想要右对齐它们时,padEnd 真的很方便。
下面的例子很好地展示了如何运用 padEnd,padStart 和 Object.entries 产生漂亮的输出。
1 | const cars = { |
Emoji 和其他双字节字符使用多个 unicode 字节表示。所以 padStart 和 padEnd 可能无法按预期工作!⚠️
例如:假设我们正在尝试用 ❤ ️表情符号填充字符串 heart 以达到10个字符。结果如下所示:
1 | // 注意,不是5颗心,只有2颗心和1颗看起来很奇怪的心! |
这是因为 ❤ ️长 2 个字符('\u2764\uFE0F')!单词 heart 本身是 5 个字符,我们要填充剩下的 5 个字符。所以 JS 先填充了两个 '\u2764\uFE0F' (也就是 ❤️❤️️) ️,最后剩下一个字节填充了 \ u2764,而它对应的是小心形 ❤。
所以结果是: ❤️❤️❤heart
PS:您可以使用此链接 查看 unicode 字符转换
此方法返回给定对象的所有属性的所有详细信息(包括 get set 方法)。主要目的是为了在浅拷贝/克隆一个对象到另一个对象中时,能把 getter 和 setter 也复制过去,因为 Object.assign 无法做到这一点。
Object.assign 浅克隆除原始源对象的 getter 和 setter 函数以外的所有详细信息。
以下示例展示了分别使用 Object.assign 和 Object.getOwnPropertyDescriptors(配合 Object.defineProperties) 以将原始对象 Car 复制到新对象 ElectricCar 时的区别。你会发现使用 Object.getOwnPropertyDescriptors 会将 discount 的 getter 和 setter 也被复制到目标对象中。
之前…
1 | // 之前... |
以后…
1 | // 以后... |
这是一个小的更新,它允许我们在最后一个函数参数后面加上尾随逗号。有什么用呢? 当你使用 git blame 时,这能确保只有新增的参数被显示出来。
以下示例展示了问题和解决方案:
1 | // 问题 |
注意:您也可以在调用函数时使用尾后逗号!
这个特性,在我看来是迄今为止最重要也是最有用的特性。异步函数把我们从回调地狱中解放出来,并使整个代码看起来很简洁。
async 关键字告诉 JavaScript 编译器以不同的方式处理函数,编译器在该函数内到达 await 关键字时暂停。它假设 await 之后的表达式返回一个 Promise,并等到 Promise 被 resolve 或 reject 后才继续前进。
在下面的例子中,getAmount 函数调用两个异步函数 getUser 和 getBankBalance。我们可以用 Promise 做到这一点,但使用 async await 更优雅和简洁。
1 | // 不使用 |
如果您要等待异步函数的结果,则需要使用 Promise 的 then 语法来捕获它。
在下面的例子中,我们想使用 console.log 在 doubleAndAdd 的外部打印出它的结果。所以我们使用 then 来等待并获取它的结果传递给 console.log。
1 |
|
在前面的例子中,我们调用了两次 await,每次调用都会等待一秒(总共两秒)。不过既然 a 和 b 没有相互依赖,我们可以使用 Promise.all 来并行调用它们。
1 | // 异步函数本身返回一个Promise |
使用 async await 时有好几种处理错误的方式。
Option 1 - 在函数中使用 try catch
1 | // 1. 使用 try catch |
Option 2 — catch 每一个 await
由于每个 await 表达式都会返回一个 Promise,因此您可以在每行上捕获错误,如下所示。
1 | // Option 2 - *Catch* 每个等待线上的错误 |
Option 3 — catch 整个 async-await 函数
1 | // Option 3 - 不要做任何事情,在function外捕获 |
ECMAScript 2018
ECMAScript 目前处于最终草案中,将于 2018 年 6 月或 7 月发布。以下所有特性均在 Stage-4 中,并将成为 ECMAScript 2018 的一部分。
1. 共享内存和原子操作 (Shared memory and atomics)
这是一个非常先进重大特性,是对 JS 引擎的核心增强。
主要的想法是为了给 JavaScript 带来一些多线程的特性,允许内存不仅仅由 JS 引擎来管理,而是由 JS 开发者们自行管理,以便将来能够编写高性能、可并发的程序。
这个特性由一个新的全局对象 SharedArrayBuffer 实现,它将数据存储在一块共享内存空间中。所以这些数据可以在 JS 主线程和 web-worker 线程之间共享。
到现在为止,如果我们想要在 JS 主线程和 web-worker 之间共享数据,我们必须复制数据并使用 postMessage 将其发送到另一个线程。没别的办法!
您只需使用 SharedArrayBuffer,数据立马就能同时被主线程和多个 web-worker 线程访问到。
但在线程之间共享内存可能会导致竞争状况。为了避免竞争条件,又引入了全局对象 Atomics(原子性)。Atomics 提供了一些方法,用来在一个线程使用共享内存的数据时给共享内存加锁。它还提供了方法来安全地更新共享内存中的(上锁的)数据。
建议通过某个库使用此特性,不过目前没有构建在此功能之上的库。
如果您有兴趣,推荐阅读:
- From Workers to Shared Memory — lucasfcosta
- A cartoon intro to SharedArrayBuffers — Lin Clark
- Shared memory and atomics — Dr. Axel Rauschmayer
首先,我们需要清楚“带标签的模板字符串(Tagged Template literal)”是什么,这样我们可以更好地理解这个特性。
Tagged template literal 是 ES2015+ 中的特性,它允许开发者自定义如何修改字符串。例如,以标准方式插入字符串,如下所示
1 | // 标准字符串插值 |
在 tagged template literal 中,你可以编写一个自定义函数(例如greet),它接收字符串文字的硬编码部分(比如 ['Hello','!'])以及要替换的变量(例如['Raja'])作为参数,返回值可以是任何东西,由你决定。
下面的例子展示了我们的自定义“Tag”函数 greet 根据当前时间为模板字符串添加相应的问候语。
1 | // "标记”函数返回一个自定义字符串文字. |
现在我们已经讨论了“标签”函数是什么,许多人想要在不同的域中使用这个功能,比如用于终端命令,或者拼装 HTTP 请求的 URI 等等。
⚠️ 标记字符串文字的问题
问题是ES2015和ES2016规范不允许使用“\u”(unicode),“\x”(十六进制)等转义字符,除非它们看起来完全像\u00A9或\uA9A或\xA9。
因此,如果您有一个内部使用其他域规则(如终端规则)的Tagged函数,那么可能需要使用\ubla123abla,它看起来不像\u0049或\u{@F804},那么您将得到语法错误。
在ES2018中,只要标记函数返回具有“cooked”属性(其中无效字符为“undefined”)的对象中的值,然后将“raw”属性(与任何你想要的)。
1 |
|
目前在正则中,虽然点号(“.”)应该匹配任一单个字符,但其实它无法匹配换行符,如 \n \r \f 等等。
比如:
1 | // 之前 |
这项改进使点号可以匹配任何单个字符。为了确保这不会破坏任何内容,我们需要给正则表达式加上 \s 标识它才会生效。
1 | //ECMAScript 2018 |
以下是提案文档中的 API:
1 | const re = /foo.bar/s; |
这种增强功能是从其他语言(如Python,Java等)引入的有用特性。称为“命名组”。该特性允许开发者以 (?<name> ...) 的形式为正则表达式中的分组进行命名。然后,他们可以使用该名称轻松获取他们需要的任何分组。
在下面的示例中,我们使用 (?<year>) (?<month>) 和 (?day) 名称对一个日期正则的不同部分进行分组。结果将会是一个包含 groups 的对象,其值又是一个包含 year,month 和 day 属性的对象,它们的值分别是对应的分组捕获。
1 | // 之前 |
我们可以使用 \k<group name> 格式在正则表达式本身中反向引用该组。以下示例展示了它的用法。
1 | //下面例子,有个组叫“fruit” |
现在,String 的 replace 实例方法也包含了命名分组的特性。所以我们可以很容易地交换字符串中的单词。
例如,把 “firstName, lastName” 变成 “lastName, firstName”。
1 | // 更换“firstName, lastName” ==> “lastName, firstName” |
rest 操作符 ...(三个点)允许我们提取尚未提取的对象属性。
1 | // 提取 firstName 和 age |
1 | //假设我们想要移除SSN,我们不需要遍历整个对象并创建一个新的cleanObj。 |
展开属性看起来就像具有三个点的 Rest 属性一样 ..., 但区别在于您使用展开来创建(重新构建)新对象。
提示:展开操作符用于等号的右侧。Rest 用于等号的左侧。
1 | // person 和 account 合并 |
这是对正则表达式的增强,它允许我们确保某些字符串出现在其他字符串之前。
您现在可以使用一个组 (?<=...)(问号,小于,等于)来查找肯定正断言。
此外,您可以使用 (?<!...)(问号,小于,感叹号)来查看否负断言。从本质上讲,只要断言通过,就会匹配。
正断言:假设我们想要确保 # 符号在单词 winning 之前存在(即:#winning)并且希望正则表达式只返回字符串“winning”。你可以这样写。
1 | /(?<=#).*/.test('winning'); // false |
负断言:假设我们想提取前面是 € 符号而不是 $ 符号的数字。
1 | // 3.00不能匹配,因为符号$在前面 |
8. 正则表达式 Unicode 属性转义(RegExp Unicode Property Escapes)
要编写正则表达式来匹配各种 Unicode 字符并不容易。像 \w,\W,\d 等只能匹配英文字符和数字。但是对于印度语,希腊语等其他语言的数字呢?
这就是 Unicode Property Escapes 的用武之地。结果是,Unicode 为每个符号(字符)添加了元数据属性,并使用它来对各种符号进行分组或描述各种符号。
例如,Unicode 数据库将所有印地语字符(हिन्दी)放在一个值为 Devanagari 的 Script 属性,和一个具有相同值 Devanagari 的 Script_Extensions的属性中。所以我们可以搜索 Script=Devanagari 得到所有印地文字符。
Devanagari(梵文)可用于马拉地语,北印度语,梵语等各种印度语言。
从 ECMAScript 2018 开始,我们可以使用 \p 转义字符和 {Script=Devanagari} 来匹配所有印度字符。也就是说,我们可以在正则表达式中使用:\p{Script=Devanagari} 来匹配所有梵文字符。
1 | //他跟随匹配多个印地文字符 |
类似地,Unicode 数据库中希腊字符的 Script_Extensions(和Script)值为Greek。所以我们可以使用 Script_Extensions=Greek 或 Script=Greek 来搜索所有希腊字符。
也就是说,我们可以在正则表达式中使用:\p{Script = Greek} 来匹配所有希腊字符。
1 | //以下匹配一个希腊字符 |
此外,Unicode 数据库将各种类型的 Emoji 存储在属性值为“true”的布尔属性 Emoji,Emoji_Component,Emoji_Presentation,Emoji_Modifier 和 Emoji_Modifier_Base 下。所以我们可以通过简单地使用 Emoji=true 来查找所有表情符号。
也就是说,我们可以使用:\p{Emoji},\Emoji_Modifier 等来匹配各种 Emoji。
下面的例子将会清楚。
1 | // 下面是匹配表情字符 |
最后,我们可以使用大写的“P”(\P)转义字符而不是小 p(\p)来否定匹配。
参考:
finally() 是 Promise 新增的实例方法。他的主要想法是无论 resolve 或是 reject 后都执行一个回调函数以帮助清理事情。finally 回调被调用时不传入参数,并且无论如何总是被执行。
我们来看看各种情况:
1 | // Resolve |
1 | // Reject |
1 | //错误情况1 |
1 |
|
这是一个*非常*有用的特性。基本上它允许我们轻松创建异步代码循环!
这个特性增加了一个新的“for-await-of”循环,允许我们循环调用返回 Promise(或 Promise 数组)的异步函数。很酷的一点是,循环会等待每个 Promise 被 resolve 才进入下一步。
1 | const promises = [ |
基本上就是这些了!