在JavaScript(ECMAScript)中,新的功能层出不穷,很难跟上步伐,更难找到有用的代码示例。因此,在本文中,我将涵盖所有列在TC39完成提案中的18个功能,这些功能都是在ES2016、ES2017和ES2018(最终草案)中添加的,并使用有用的示例展示它们。
这是一篇相当长的文章,但应该很容易阅读。把它当作“Netflix的连续阅读”。通过本文,我保证你会对所有这些功能有很多了解。
好的,让我们逐一讨论这些功能。
Array.prototype.includes
includes
是Array的一个简单实例方法,帮助我们轻松地查找一个项是否在数组中(与indexOf
不同,它包括NaN
)。
Trivia:JavaScript规范的人们想把它命名为contains
,但显然Mootools已经使用了这个名字,所以他们使用了includes
。
Exponentiation
中缀运算符
像加法和减法这样的数学运算具有中缀运算符,如+
和-
。与它们类似,**
中缀运算符通常用于指数运算。在ECMAScript 2016中,引入了**
代替了Math.pow
。
Object.values()
Object.values()
是一个新的函数,类似于Object.keys()
,但返回对象自身属性的所有值,不包括原型链中的任何值。
Object.entries()
Object.entries()
与Object.keys()
相关,但它不仅返回键,还以数组方式返回键和值。这使得在循环中使用对象或将对象转换为Map非常简单。
- 字符串填充
String添加了两个实例方法——String.prototype.padStart
和String.prototype.padEnd
——允许将空字符串或其他字符串附加/预先附加到原始字符串的开头或结尾。
这在我们想要对齐事物的情况下非常方便,例如在漂亮的打印显示或终端打印中。
padStart示例:
在下面的示例中,我们有一个数字列表,长度不同。我们想要在显示目的上在所有项目之前加上“0”,以便所有项目都具有相同的10位数字长度。我们可以使用padStart(10,'0')
轻松实现这一点。
padEnd示例:
当我们打印多个长度不同的项目并想要正确地右对齐它们时,padEnd
确实非常有用。
下面的示例是一个很好的实际示例,展示了padEnd
,padStart
和Object.entries
如何结合起来产生美丽的输出。
⚠️在表情符号和其他双字节字符上使用padStart和padEnd
表情符号和其他双字节字符使用多个字节的Unicode表示。因此,padStart和padEnd可能无法按预期工作!⚠️
例如:假设我们正在尝试使用❤️表情符号将字符串“heart”填充到达到10个字符的长度。结果将如下所示:
这是因为❤️有2个代码点长('\u2764\uFE0F'
)!单词“heart”本身有5个字符,因此我们只剩下5个字符要填充。所以发生的事情是JS使用'\u2764\uFE0F'
填充了两个心形符号,并产生了❤️❤️。对于最后一个字符,它只使用心形符号的第一个字节\u2764
,这会产生❤。
所以我们最终得到:❤️❤️❤heart
PS:你可以使用此链接查看Unicode字符转换。
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
var Car = {
name: 'BMW',
price: 1000000,
set discount(x) {
this.d = x;
},
get discount() {
return this.d;
},
};//Print details of Car object's 'discount' property
console.log(Object.getOwnPropertyDescriptor(Car, 'discount'));
//prints..
// {
// get: [Function: get],
// set: [Function: set],
// enumerable: true,
// configurable: true
// }//Copy Car's properties to ElectricCar using Object.assign
const ElectricCar = Object.assign({}, Car);//Print details of ElectricCar object's 'discount' property
console.log(Object.getOwnPropertyDescriptor(ElectricCar, 'discount'));
//prints..
// {
// value: undefined,
// writable: true,
// enumerable: true,
// configurable: true
// }
//⚠️Notice that getters and setters are missing in ElectricCar object for 'discount' property !👎👎//Copy Car's properties to ElectricCar2 using Object.defineProperties
//and extract Car's properties using Object.getOwnPropertyDescriptors
const ElectricCar2 = Object.defineProperties({}, Object.getOwnPropertyDescriptors(Car));//Print details of ElectricCar2 object's 'discount' property
console.log(Object.getOwnPropertyDescriptor(ElectricCar2, 'discount'));
//prints..
// { get: [Function: get], 👈🏼👈🏼👈🏼
// set: [Function: set], 👈🏼👈🏼👈🏼
// enumerable: true,
// configurable: true
// }
// Notice that getters and setters are present in the ElectricCar2 object for 'discount' property!
5. 在函数参数中添加尾逗号
这是一个次要的更新,允许我们在最后一个函数参数后添加尾逗号。为什么?为了帮助像git blame这样的工具,以确保只有新开发人员受到指责。
下面的示例显示了问题和解决方案。
ECMAScript 2017(ES 8)—函数参数中的尾逗号
注意:你也可以使用尾逗号调用函数!
6. Async/Await
到目前为止,如果你问我,这是最重要和最有用的功能。异步函数使我们不必处理回调地狱,并使整个代码看起来简单。
async
关键字告诉JavaScript编译器以不同的方式处理该函数。每当编译器在该函数中达到await
关键字时,它就会暂停。它假定await
之后的表达式返回一个Promise,并等待Promise被解析或拒绝,然后再继续。
在下面的示例中,getAmount
函数调用两个异步函数getUser
和getBankBalance
。我们可以使用Promise来实现,但是使用async await
更加优雅和简单。
ECMAScript 2017(ES 8)—异步等待基本示例
6.1 Async函数本身返回Promise。
如果你正在等待异步函数的结果,则需要使用Promise的then
语法来捕获其结果。
在以下示例中,我们想使用console.log
记录结果,但不在doubleAndAdd
中。因此,我们要等待并使用then
语法将结果传递给console.log
。
ECMAScript 2017(ES 8)—异步等待本身返回Promise
6.2 并行调用async/await
在上一个示例中,我们调用了两次await
,但是每次我们都等待一秒钟(总共2秒钟)。相反,我们可以并行化,因为a
和b
不相互依赖,使用Promise.all
。
ECMAScript 2017(ES 8)—使用Promise.all并行化async/await
6.3 错误处理async/await函数
使用async await处理错误的方法有很多种。
选项1 — 在函数内部使用try catch
ECMAScript 2017 — 在async/await函数内部使用try catch
//Option 1 - Use try catch within the function
async function doubleAndAdd(a, b) {
try {
a = await doubleAfter1Sec(a);
b = await doubleAfter1Sec(b);
} catch (e) {
return NaN; //return something
}return a + b;
}
//🚀Usage:
doubleAndAdd('one', 2).then(console.log); // NaN
doubleAndAdd(1, 2).then(console.log); // 6function doubleAfter1Sec(param) {
return new Promise((resolve, reject) => {
setTimeout(function() {
let val = param * 2;
isNaN(val) ? reject(NaN) : resolve(val);
}, 1000);
});
}
选项2 — 捕获每个await表达式
由于每个await
表达式都返回一个Promise,因此你可以像下面显示的那样在每行上捕获错误。
ECMAScript 2017 — 在每个await表达式上使用try catch
//Option 2 - *Catch* errors on every await line
//as each await expression is a Promise in itself
async function doubleAndAdd(a, b) {
a = await doubleAfter1Sec(a).catch(e => console.log('"a" is NaN')); // 👈
b = await doubleAfter1Sec(b).catch(e => console.log('"b" is NaN')); // 👈
if (!a || !b) {
return NaN;
}
return a + b;
}//🚀Usage:
doubleAndAdd('one', 2).then(console.log); // NaN and logs: "a" is NaN
doubleAndAdd(1, 2).then(console.log); // 6function doubleAfter1Sec(param) {
return new Promise((resolve, reject) => {
setTimeout(function() {
let val = param * 2;
isNaN(val) ? reject(NaN) : resolve(val);
}, 1000);
});
}
选项3 — 在最后捕获整个async/await函数
ECMAScript 2017 — 在最后捕获整个async/await函数
//Option 3 - Dont do anything but handle outside the function
//since async / await returns a promise, we can catch the whole function's error
async function doubleAndAdd(a, b) {
a = await doubleAfter1Sec(a);
b = await doubleAfter1Sec(b);
return a + b;
}//🚀Usage:
doubleAndAdd('one', 2)
.then(console.log)
.catch(console.log); // 👈👈🏼<------- use "catch"function doubleAfter1Sec(param) {
return new Promise((resolve, reject) => {
setTimeout(function() {
let val = param * 2;
isNaN(val) ? reject(NaN) : resolve(val);
}, 1000);
});
}
ECMAScript目前处于最终草案阶段,将于2018年6月或7月发布。下面介绍的所有功能都处于Stage-4,并将成为ECMAScript 2018的一部分。
1. 共享内存和原子操作```
这是一个非常庞大、先进的功能,是JS引擎的核心增强。
主要思想是将一些多线程功能引入JavaScript,以便JS开发人员可以编写高性能的并发程序,通过允许自己管理内存来管理内存,而不是让JS引擎来管理内存。
这是通过一种名为SharedArrayBuffer的全局对象来实现的,该对象基本上将数据存储在一个共享 内存空间中。因此,这些数据可以在主JS线程和Web Worker线程之间共享。
到目前为止,如果我们想在主JS线程和Web Worker之间共享数据,我们必须复制数据并使用postMessage
将其发送到其他线程。现在不用了!
你只需使用SharedArrayBuffer,数据即可立即被主线程和多个Web Worker线程访问。
但在线程之间共享内存可能会导致竞态条件。为了避免竞态条件,引入了“Atomics”全局对象。_Atomics_提供了各种方法,在线程使用其数据时锁定共享内存。它还提供了安全更新共享内存中这些数据的方法。
建议通过某个库来使用此功能,但目前还没有建立在此功能之上的库。
如果你感兴趣,我建议阅读:
2. 消除标记模板文字限制
首先,我们需要澄清什么是“标记模板文字”,以便更好地理解此功能。
在ES2015+中,有一个名为标记模板文字的功能,允许开发人员自定义字符串插值的方式。例如,在标准方式下,字符串插值如下所示…
在标记文字中,你可以编写一个函数来接收字符串文字的硬编码部分,例如 [ ‘Hello ‘, ‘!’ ]
,以及替换变量,例如 [ 'Raja']
,作为自定义函数(例如 greet
)的参数,并从该自定义函数返回任何你想要的内容。
下面的示例显示了我们的自定义“标记”函数greet
将一天中的时间(如“早上好!”,“下午好!”等)附加到字符串文字中,并返回自定义字符串。
标记函数示例,显示自定义字符串插值
//A "Tag" function returns a custom string literal.
//In this example, greet calls timeGreet() to append Good //Morning/Afternoon/Evening depending on the time of the day.function greet(hardCodedPartsArray, ...replacementPartsArray) {
console.log(hardCodedPartsArray); //[ 'Hello ', '!' ]
console.log(replacementPartsArray); //[ 'Raja' ]let str = '';
hardCodedPartsArray.forEach((string, i) => {
if (i < replacementPartsArray.length) {
str += `${string} ${replacementPartsArray[i] || ''}`;
} else {
str += `${string} ${timeGreet()}`; //<-- append Good morning/afternoon/evening here
}
});
return str;
}//🚀Usage:
const firstName = 'Raja';
const greetings = greet`Hello ${firstName}!`; //👈🏼<-- Tagged literalconsole.log(greetings); //'Hello Raja! Good Morning!' 🔥function timeGreet() {
const hr = new Date().getHours();
return hr < 12
? 'Good Morning!'
: hr < 18 ? 'Good Afternoon!' : 'Good Evening!';
}
现在我们讨论了“标记”函数是什么,许多人希望在不同的领域中使用此功能,例如在终端中用于命令和HTTP请求中用于组合URI等。
⚠️标记字符串文字的问题
问题在于,ES2015和ES2016规范不允许使用转义字符,例如“\u”(unicode)、“\x”(十六进制),除非它们看起来完全像\u00A9
或\u{2F804}
或\xA9
。
因此,如果你有一个内部使用其他领域规则的标记函数(例如终端规则)的标记函数,那么可能需要使用**\ubla123abla**这样看起来不像\u0049或\u{@F804}的字符,那么你将会得到语法错误。
在ES2018中,规则被放松,允许使用这样看似无效的转义字符,只要标记函数将值返回为带有“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
这个增强功能使点运算符能够匹配任何单个字符。为了确保这不会破坏任何内容,我们需要在创建RegEx时使用\s
标志才能使其正常工作。
//ECMAScript 2018
/first.second/s.test('first\nsecond'); //true Notice: /s 👈🏼
以下是提案文档中的总体API:
ECMAScript 2018 — Regex dotAll功能允许通过/s标志匹配任何字符,包括\n
4. RegExp命名组捕获 🔥
这个增强功能将其他语言(如Python、Java等)中称为“命名组”的有用的RegExp功能引入了JavaScript。此功能允许编写RegExp的开发人员为RegExp中不同部分的组提供格式为(?<name>...)
的名称(标识符)。然后,他们可以使用该名称轻松获取他们需要的任何组。## 4.1 命名分组基本示例
在下面的示例中,我们使用 (?<year>)
, (?<month>)
和 (?<day>)
名称来分组日期正则表达式的不同部分。生成的对象现在将包含一个 groups
属性,其中包含相应值的 year
、month
和 day
属性。
ECMAScript 2018 — 正则表达式命名分组示例
4.2 在正则表达式中使用命名分组
我们可以使用 \k<group name>
格式在正则表达式本身内部回溯分组。下面的示例展示了它的工作原理。
ECMAScript 2018 — 正则表达式命名分组通过 \k 回溯
4.3 在 String.prototype.replace 中使用命名分组
现在,命名分组功能已经内置到了 String 的 replace
实例方法中。因此,我们可以轻松地交换字符串中的单词。
例如,将“firstName,lastName”更改为“lastName,firstName”。
ECMAScript 2018 — 在 replace 函数中使用正则表达式的命名分组功能
5. 对象的剩余属性
剩余运算符 ...
(三个点)允许我们提取尚未提取的对象属性。
5.1 你可以使用剩余属性来帮助提取你想要的属性
ECMAScript 2018 — 剩余运算符解构对象
5.2 更好的是,你可以删除不想要的项!🔥🔥
ECMAScript 2018 — 剩余运算符解构对象
6. 对象的扩展属性
扩展属性看起来与剩余属性完全相同,都是三个点 ...
,但区别在于你使用扩展来创建(重构)新对象。
提示:扩展运算符用于等号的右侧。而剩余运算符用于等号的左侧。
ECMAScript 2018 — 扩展解构对象
7. RegExp 向后断言
这是 RegEx 的一个增强功能,允许我们确保某些字符串在另一个字符串之前立即存在。
现在,你可以使用组 (?<=...)
(问号,小于,等于)来查找正面断言。
此外,你可以使用 (?<!...)
(问号,小于,感叹号)来查找负面断言。本质上,只要负面断言通过,就会匹配。
正面断言:假设我们想确保单词“winning”之前存在 #
符号(即 #winning
),并且希望正则表达式仅返回字符串“winning”。下面是你的编写方式。
ECMAScript 2018 — (?<=...)
用于正面断言
负面断言:假设我们想从具有 € 符号但在数字之前没有 $ 符号的行中提取数字。
ECMAScript 2018 — (?<!...)
用于负面断言
8. RegExp Unicode Property Escapes
编写用于匹配各种 Unicode 字符的 RegEx 并不容易。例如,像 \w
、\W
、\d
等只匹配英文字符和数字。但是其他语言(如印地语、希腊语等)中的数字呢?
这就是 Unicode 属性转义的作用。Unicode 为每个符号(字符)添加元数据属性,并使用它来分组或描述各种符号。
例如,Unicode 数据库将所有印地语字符(हिन्दी)分组到一个名为 Script
的属性下,其值为 Devanagari
,并将其分组到另一个名为 Script_Extensions
的属性下,其值也为 Devanagari
。因此,我们可以搜索 Script=Devanagari
并获取所有印地语字符。
从 ECMAScript 2018 开始,我们可以使用 \p
转义字符,加上 {Script=Devanagari}
来匹配所有这些印地语字符。也就是说,我们可以在正则表达式中使用:\p{Script=Devanagari}
来匹配所有 Devanagari 字符。
ECMAScript 2018 — 显示 \p
//The following matches multiple hindi character
/^\p{Script=Devanagari}+$/u.test('हिन्दी'); //true
//PS:there are 3 hindi characters h
同样地,Unicode 数据库将所有希腊字符都分组到了 Script_Extensions
(和 Script
)属性中,属性值为 Greek
。因此,我们可以使用 Script_Extensions=Greek
或 Script=Greek
来搜索所有希腊字符。
也就是说,我们可以在正则表达式中使用:\p{Script=Greek}
** 来匹配所有希腊字符。**
ECMAScript 2018 — 显示 \p
//The following matches a single Greek character
/\p{Script_Extensions=Greek}/u.test('π'); // true
此外,Unicode 数据库将各种类型的表情存储在布尔属性 Emoji
、Emoji_Component
、Emoji_Presentation
、Emoji_Modifier
和 Emoji_Modifier_Base
中,属性值为 true
。因此,我们可以通过选择 Emoji
为 true 来搜索所有表情。
也就是说,我们可以使用:\p{Emoji}
,\Emoji_Modifier
** 等来匹配各种类型的表情。**
下面的示例将说明一切。
ECMAScript 2018 — 显示如何使用 \p 匹配各种表情
//The following matches an Emoji character
/\p{Emoji}/u.test('❤️'); //true//The following fails because yellow emojis don't need/have Emoji_Modifier!
/\p{Emoji}\p{Emoji_Modifier}/u.test('✌️'); //false//The following matches an emoji character\p{Emoji} followed by \p{Emoji_Modifier}
/\p{Emoji}\p{Emoji_Modifier}/u.test('✌🏽'); //true//Explaination:
//By default the victory emoji is yellow color.
//If we use a brown, black or other variations of the same emoji, they are considered
//as variations of the original Emoji and are represented using two unicode characters.
//One for the original emoji, followed by another unicode character for the color.
//
//So in the below example, although we only see a single brown victory emoji,
//it actually uses two unicode characters, one for the emoji and another
// for the brown color.
//
//In Unicode database, these colors have Emoji_Modifier property.
//So we need to use both \p{Emoji} and \p{Emoji_Modifier} to properly and
//completely match the brown emoji.
/\p{Emoji}\p{Emoji_Modifier}/u.test('✌🏽'); //true
最后,我们可以使用大写的 "P"(\P
)转义字符,而不是小写的 p(\p
),来否定匹配。
参考:
8. Promise.prototype.finally()
finally()
是添加到 Promise 中的新实例方法。其主要思想是允许在 resolve
或 reject
之后运行回调以帮助清理事务。finally
回调被调用时不带任何值,并且无论如何都会执行。
让我们看看各种情况。
ECMAScript 2018 — 在 resolve 情况下的 finally()
ECMAScript 2018 — 在 reject 情况下的 finally()
ECMASCript 2018 — 在 Promise 中抛出错误的 finally()
ECMAScript 2018 — 在 catch 中抛出错误的 finally()
9. 异步迭代
这是一个非常有用的功能。基本上,它允许我们轻松创建异步代码的循环!
此功能添加了一个新的 “for-await-of” 循环,允许我们在循环中调用返回 Promise(或一堆 Promise 的数组)的异步函数。酷的是,该循环在进行下一个循环之前等待每个 Promise 解析。
ECMAScript 2018 — 通过 for-await-of 实现异步迭代
评论(0)