我们很难及时得知JavaScript(ECMAScript)中的最新功能,同时找到相关的示例代码更加困难。
为了解决这个问题,我将在本文中介绍在ES2016,ES2017和ES2018(最终草案)中添加的18个功能,这些功能在TC39’s finished proposals 中列出,并展示相关的例子。
尽管这篇文章很长,不过应该很容易读懂。 你可以把它想象成“Netflix binge reading”。到此为止,我保证你将对所有这些功能有很多了解。

接下来逐一讨论这些问题。

ECMAScript 2016
1. Array.prototype.includes
includes 是Array上的一个简单实例方法,能帮助我们轻松查找某项是否存在于数组中(处理 NaN 的方式与 indexOff不同)。

(ECMAScript 2016 or ES7 — Array.prototype.includes() )
小细节: 在 JavaScript 规范中人们想要把这个方法命名为
contains, 但是这个名字已经被 Mootools 使用了,所以只好被命名为includes.
2. 幂运算符
加法和减法等数学运算分别对应 + 和 - 运算符。 同样,**运算符通常用于指数运算。 在ECMAScript 2016中,引入 ** 来代替Math.pow。

(ECMAScript 2016 or ES7 — 幂运算符** )

ECMAScript 2017
1. Object.values()
Object.values()是一个与Object.keys()类似的新函数,不过它返回的是Object自身属性的所有值,不包括原型链中的任何值。

(ECMAScript 2017 (ES8)— Object.values() )
2. Object.entries()
Object.entries()与Object.keys相关,但它并不是仅返回键,而是以数组方式返回键和值。 这样一来,在循环中使用对象或将对象转换为Maps等操作将会变得非常简单。
Example 1:

(ECMAScript 2017 (ES8) — 在循环中使用 Object.entries() )
Example 2:

(ECMAScript 2017 (ES8) — 使用 Object.entries() 将 Object 转化为 Map)
3.字符串填充
String 中添加了两个实例方法—— String.prototype.padStart和String.prototype.padEnd ,允许将空字符串或其他字符串附加/前置到原始字符串的开头或结尾。
'someString'.padStart(numberOfCharcters [,stringForPadding]);
'5'.padStart(10) // ' 5'
'5'.padStart(10, '=*') //'=*=*=*=*=5'
'5'.padEnd(10) // '5 '
'5'.padEnd(10, '=*') //'5=*=*=*=*='
可以在想要用漂亮的格式打印输出时或者打印对齐等场景中派上用场。
3.1 padStart 示例:
下面的示例中列出了不同长度的数字。 我们希望前置“0”,以便在显示时所有项目都具有相同的10位长度。 使用padStart(10, '0')轻松实现这一目标。

(ECMAScript 2017 — padStart 示例 )
3.2 padEnd 示例:
当我们打印不同长度的多个项目并希望它们正确对齐时,padEnd真的很方便。
下面的示例是padEnd,padStart和Object.entries如何组合在一起以产生漂亮输出的一个很好的现实示例。

(ECMAScript 2017 — padEnd, padStart 和 Object.Entries 示例 )
3.3 ⚠️ 使用padStart 与 padEnd 处理 Emojis 和其它双字节字符
Emojis和其他双字节字符使用多个字节的unicode表示。 所以padStart和padEnd可能无法按预期工作!⚠️
例如:假设我们想要在字符串中填充10个❤️表情符号,结果如下所示:
//Notice that instead of 5 hearts, there are only 2 hearts and 1 heart that looks odd!
'heart'.padStart(10, "❤️"); // prints.. '❤️❤️❤heart'
这是因为❤️的长度是2个码点('\ u2764 \ uFE0F')! 单词heart本身是5个字符,所以我们只剩下5个字符来填充。 所以会发生什么事情,JS使用’\ u2764 \ uFE0F’来填充两颗心并产生❤️❤️。 对于最后一个,它只使用第一个码点\ u2764产生❤字符。
所以我们最终得到:❤️❤️❤heart
PS:可以通过此链接(https://encoder.internetwache.org/#tab_uni )查看怎样对unicode char进行转换。
4. Object.getOwnPropertyDescriptors
此方法返回给定对象的所有属性的全部详细信息(包括getter方法 get 和 setter方法 set)。 添加它的主要目的是允许浅层拷贝/克隆对象到另一个对象,该对象也复制getter和setter函数而不是Object.assign。
Object.assign 用于浅层拷贝除了原始源对象的getter和setter函数之外的所有细节。
下面的示例显示了Object.assign和Object.getOwnPropertyDescriptors以及Object.defineProperties之间的区别,以将原始对象Car复制到新对象ElectricCar中。 你将看到,通过使用Object.getOwnPropertyDescriptors,discount 的getter和setter函数也会复制到目标对象中。
以前

(以前 — 使用 Object.assign )
之后

(ECMAScript 2017 (ES8) — Object.getOwnPropertyDescriptors )
5.在函数参数中添加尾随逗号
这是一个次要更新,允许我们在最后一个函数参数后面有逗号。 为什么? 帮助使用像git blame这样的工具来确保只有新的开发人员的代码被标注。
以下示例显示了问题和解决方案。

注意:也可以使用尾随逗号调用函数!
6. Async/Await
到目前为止,这是最重要和最有用的功能。 异步函数允许我们不必处理回调并使整个代码看起来很简单。
async关键字告诉JavaScript编译器以不同方式处理函数。 只要到达该函数中的await关键字,编译器就会暂停。 它假定await之后的表达式返回一个promise并等待,直到promise被解决或被拒绝,然后才进一步移动。
在下面的示例中,getAmount函数调用两个异步函数getUser和getBankBalance。 我们可以做到这一点,但是使用async await更加优雅和简单。

(ECMAScript 2017 (ES 8) — Async Await 基本示例 )
6.1异步函数本身返回一个Promise。
如果您正在等待异步函数的结果,则需要使用Promise的then语法来捕获其结果。
在以下示例中,我们希望使用console.log而不是在doubleAndAdd函数中记录结果。 所以我们想等待并使用then语法将结果传递给console.log。

(ECMAScript 2017 (ES 8) — Async Await themselves returns Promise )
6.2 并行调用 async/await
在前面的例子中,我们调用了两次await,每次会等待一秒钟(总共2秒)。 不过我们可以并行化处理它,因为a和b不使用Promise.all相互依赖。

6.3 async/await错误处理功能
使用异步等待时,有多种方法可以处理错误。
选项1 - 在函数中使用try catch

(ECMAScript 2017 - 在async/await函数中使用try catch)
选项2-捕获每个等待表达式
由于每个await表达式都返回一个Promise,因此可以捕获每行的错误,如下所示。

(ECMAScript 2017 — Use try catch every await expression)
选项3 - 捕获整个async-await函数

(ECMAScript 2017 — Catch the entire async/await function at the end)

ECMAScript 2018
ECMAScript目前正在最终草案中,将于2018年6月或7月结束。下面介绍的所有功能都在第4阶段,并将成为ECMAScript 2018的一部分。
1.共享内存和Atomics
这是一个巨大的,非常先进的功能,是JS引擎的核心增强功能。
主要思想是为JavaScript提供某种多线程功能,以便JS开发者可以通过自己管理内存——而不是让JS引擎管理内存——来编写高性能的并发程序。
这是通过一种名为 SharedArrayBuffer 的新型全局对象完成的,该对象实质上将数据存储在共享内存空间中。因此,这些数据可以在主JS线程和Web工作线程之间共享。
到目前为止,如果我们想在主JS线程和Web工作者之间共享数据,就必须复制数据并使用postMessage将其发送到另一个线程。以后不会再这样了!
只需使用SharedArrayBuffer,主线程和多个Web工作线程都可以立即访问数据。
但是在线程之间共享内存会导致竞争条件。为了帮助避免竞争条件,引入了“Atomics”全局对象 。 Atomics提供了各种方法,使得线程在使用其数据时锁定共享内存。它还提供了安全地更新共享内存中数据的方法。
建议通过某个库使用此功能,但是现在没有基于此功能构建的库。 如果你有兴趣,我建议阅读:
- From Workers to Shared Memory — lucasfcosta
- A cartoon intro to SharedArrayBuffers — Lin Clark
- Shared memory and atomics — Dr. Axel Rauschmayer
2.删除了标记模板文字限制
首先,我们需要澄清“标记模板文字”是什么,以便我们更好地理解这个功能。
在ES2015 +中,有一个称为标记模板文字的功能,允许开发人员自定义字符串的插值方式。 例如,在标准方式中,字符串被插入如下…

在标记的文字中,你可以编写一个函数来接收字符串文字的硬编码部分,例如['Hello','!'],或者替换变量,例如['Raja'],作为参数进入自定义函数(例如greet),并从该自定义函数返回您想要的任何内容。
下面的示例演示自定义“标记”函数greet,根据当前时间返回例如“Good Morning!” “Good afternoon!”之类的字符串。

(显示自定义字符串插值的标记功能示例 )
现在我们讨论了“Tagged”函数是什么,许多人想要在不同的领域中使用此功能,例如在终端中使用命令行或HTTP请求来拼接URIs等等。
⚠️标记字符串字符的问题
问题是在ES2015和ES2016规范不允许使用转义字符,如“\u”(unicode),“\x”(十六进制),除非它们看起来完全像\u00A9或\u{2F804}或\xA9。
因此,如果你有一个Tagged函数在内部使用其他领域的规则(如终端的规则),可能需要使用**\ubla123abla**这样的字符,它看起来一点也不像\u0049或\u {@F804}的样子,最后你将会得到一个语法错误。
不过在ES2018中,只需要Tagged函数返回一个具有“cooked”属性(赋值为“undefined”)和“raw”属性( 你想要的任何内容)的对象即可。
function myTagFunc(str) {
return { "cooked": "undefined", "raw": str.raw[0] }
}
var str = myTagFunc `hi \ubla123abla`; //call myTagFunc
str // { cooked: "undefined", raw: "hi \\unicode" }
3.正则表达式的“dotall”标志
目前在正则表达式中,虽然点(“.”)应该与单个字符匹配,但它不能与\n \r \f等新行字符匹配。
例如:
//Before
/first.second/.test('first\nsecond'); //false
这个功能使点运算符可以匹配任何单个字符。 为了确保不会破坏任何内容,我们需要在创建正则表达式时使用\s标志才能使其正常工作。
//ECMAScript 2018
/first.second/s.test('first\nsecond'); //true Notice: /s 👈🏼
以下是提案文档中的全部API:(https://github.com/tc39/proposal-regexp-dotall-flag )

(ECMAScript 2018 — Regex dotAll feature allows matching even \n via “.” via /s flag )
4. 正则表达式命名组🔥
此增强功能带来了其他语言(如Python,Java等)具有的正则功能,称为“命名组”。能够允许开发者编写正则表达式,通过格式(?<name>...)提供不同部分的名称(标识符)来进行分组。 这样一来就可以使用该名称轻松得到需要的任何分组。
4.1基本命名组示例
在下面的示例中,我们使用(?<year>) (?<month>) and (?<day>)名称对日期正则的不同部分进行分组。 生成的对象将包含一个groups属性,在groups属性中存在相应值的year, month 和 day属性。
(ECMAScript 2018 — Regex named groups example)
4.2在正则表达式内使用命名组
我们可以使用 \k<group name> 格式来反向引用正则表达式本身中的组。 以下示例显示了它的工作原理。

(ECMAScript 2018 — Regex named groups back referencing via \k<group name> )
4.3在String.prototype.replace中使用命名组
命名组功能现在被内置到String的 replace 实例方法中。 所以我们可以轻松地替换字符串中的单词。
例如,将“firstName,lastName” 更改为“lastName,firstName”。

(ECMAScript 2018 — Using RegEx’s named groups feature in replace function)
5. 对象的rest属性
Rest运算符 ...(三个点)允许我们在提取Object属性时丢弃一些属性。
5.1 使用rest来帮助仅提取所需的属性

(ECMAScript 2018 — Object destructuring via rest)
5.2 更酷的是,你可以删除不需要的项目!🔥🔥

(ECMAScript 2018 — Object destructuring via rest)
6.对象的Spread属性
Spread属性看起来就像具有三个点的rest属性...但不同之处在于使用spread来创建(重构)新对象。
提示:展开运算符用于等号的右侧。 其余的用在等号的左侧。

(ECMAScript 2018 — Object restructuring via spread )
7. 正则 Lookbehind 断言
这是对正则表达式的一种增强,它允许我们确认某些字符在其他字符串之前。
现在可以使用一个组 (?<=…) (问号,小于,等于)来判断前向断言。
此外,也可以使用 (?<!…) (问号,小于,感叹号)来查看否定断言。 基本上,只要-ve断言通过,就会匹配。
积极断言: 假设我们要确保#符号存在于 winning 之前(即: #winning),并希望正则表达式只返回字符串“winning”。应该这样写。

(ECMAScript 2018 — (?<=…) for positive assertion )
否定断言: 假设我们想要从具有€符号的行中提取数字,同时忽略带有$符号的数字。

(ECMAScript 2018 — (?<!…) for negative assertions )
8. RegEx Unicode Property Escapes
编写匹配各种unicode字符的正则表达式并不容易。 像 \w , \W , \d 等的东西只匹配英文字符和数字。 但是其他语言如印地语,希腊语等中的数字该怎么处理呢?
这就是Unicode Property Escapes的用武之地。实际上,Unicode为每个符号(字符)添加元数据属性,并使用它来分组或表征各种符号。
例如,Unicode数据库将所有印地语字符(हिन्दी)归为一个名为Script的属性,其值为Devanagari,另一个属性为Script_Extensions,其值为Devanagari。 所以我们可以搜索Script = Devanagari并获得所有印地语字符。
梵文可以用于各种印度语言,如马拉地语,印地语,梵语等。
从ECMAScript 2018开始,可以用\p来转义字符以及用{Script = Devanagari}来匹配所有这些印度字符。 也就是说,我们可以在RegEx中使用:\p{Script = Devanagari}来匹配所有梵文字符。

(ECMAScript 2018 — showing \p)
同样,Unicode数据库将Script_Extensions(和Script)属性下的所有希腊字符组合为希腊语。 所以我们可以使用Script_Extensions = Greek或Script = Greek搜索所有希腊字符。
也就是说,我们可以在RegEx中使用:\p{Script = Greek}来匹配所有希腊字符。

(ECMAScript 2018 — showing \p)
此外,Unicode数据库在布尔属性Emoji,Emoji_Component,Emoji_Presentation,Emoji_Modifier和Emoji_Modifier_Base下存储各种类型的Emojis,其属性值为“true”。 因此,我们只需选择表情符号即可搜索所有表情符号。
也就是说,我们可以使用: \p{Emoji} ,\Emoji_Modifier 等来匹配各种表情符号。
以下示例将演示这一点。

(ECMAScript 2018 — showing how \p can be used for various emojis)
最后,我们可以使用转义字符大写“P”(\P)而不是小p(\p)来进行否定匹配。
参考文献:
- ECMAScript 2018提案(https://mathiasbynens.be/notes/es-unicode-property-escapes)
- https://mathiasbynens.be/notes/es-unicode-property-escapes
8. Promise.prototype.finally()
finally()是一个添加到Promise的新实例方法。 其主旨是允许在resolve 或 reject后运行回调以帮助清理。 finally的回调被调用时而没有任何参数,同时任何情况下都会被执行。
来看看各种情形。

(ECMAScript 2018 — finally() in resolve case)

(ECMAScript 2018 — finally() in reject case)

(ECMASCript 2018 — finally() in Error thrown from Promise case)

(ECMAScript 2018 — Error thrown from within catch case)
9.异步迭代
这是一个非常有用的功能。 它允许我们轻松的创建异步代码循环!
此功能添加了一个新的**“for-await-of”**循环,允许我们在循环中调用返回promises(或带有一堆promise的Arrays)的异步函数。更酷的是循环会在在进行下一个循环之前等待每个Promise。

(ECMAScript 2018 — Async Iterator via for-await-of)
就是以上这些!