x-note
Search…
ECMAScript 2018
ECMAScript 2018 中依旧只做了少部分的变更,其中包括:
  • 新增 Promise.prototype.finally()
  • 新增 Rest/Spread Properties
  • 新增 Asynchronous iteration
  • 对字符串模版进行修订
  • 正则表达式功能调整
    • s (dotAll) flag for regular expressions
    • RegExp named capture groups
    • RegExp Unicode property escapes
    • RegExp lookbehind assertions

Promise.prototype.finally()

ECMAScript 2018 的重大变更之一,解决了原先许多的痛点。类似于同步代码块中的 finally {} 部分。
1
try{
2
}catch(e){
3
}finally{
4
}
Copied!
基本用法:
1
let connection;
2
db.open()
3
.then(conn => {
4
connection = conn;
5
return connection.select({ name: 'Jane' });
6
})
7
.then(result => {
8
// Process result
9
// Use `connection` to make more queries
10
})
11
// ···
12
.catch(error => {
13
// handle errors
14
})
15
.finally(() => {
16
connection.close();
17
});
Copied!

Rest/Spread Properties

在此之前,对象解构中的其余运算符 ... 仅适用于数组解构和参数定义。对象文字中的扩展运算符 ... 仅适用于数组文字以及函数和方法调用。
虽然大多数人用于该操作符处理对象已经很久了,但该公能在 ES2019 中才正式发布
rest operator ... 会拷贝所有的可枚举属性
1
const {foo, ...rest } = obj;
2
const _rest = { ...rest };
3
4
function func({ param1, param2, ...rest }) { // rest operator
5
console.log('All parameters: ', { param1, param2, ...rest }); // spread operator
6
return param1 + param2;
7
}
Copied!
根据每个对象文字的顶级,您最多可以使用一次 rest ... 运算符,它必须出现在结尾处:
1
const {...rest, foo} = obj; // SyntaxError
2
const { foo, ...rest1, ...rest2 } = obj; // SyntaxError
Copied!
在对象文字内部,扩展运算符(...)将其操作数的所有可枚举属性插入到通过文字创建的对象中:
1
const obj = {foo: 1, bar: 2, baz: 3}
2
console.log({...obj, foo: true}) // {foo: true, bar: 2, baz: 3}
3
console.log({ foo: true, ...obj }) // {foo: 1, bar: 2, baz: 3}
Copied!
属性的扩展运算符(...),类似于 Object.assign()

Asynchronous iteration

ES6 内置了对同步迭代数据的支持。 但是异步传输的数据呢? 例如,从文件或HTTP连接异步读取的文本行。
ES6 中同步迭代工作原理如下:
  • Iterable:一个对象,通过一个键为 Symbol.iterator 的方法表示它可以迭代。
  • Iterator:通过在 iterable 上调用[Symbol.iterator]() 返回的对象。 它将每个迭代元素包装在一个对象中,并通过其 next() 方法返回它 - 一次一个。
  • IteratorResult:next()返回的对象。 属性值包含一个迭代元素,在最后一个元素之后属性donetrue(通常可以忽略值; 它几乎总是未定义)。
先前解释的迭代方式是同步的,它不适用于异步数据源。 例如,在以下代码中,readLinesFromFile()无法通过同步迭代传递其异步数据:
1
for (const line of readLinesFromFile(fileName)) {
2
console.log(line);
3
}
Copied!
Asynchronous iteration 是一种异步工作的新迭代协议:
异步迭代通过Symbol.asyncIterator标记。 异步迭代器的方法next()返回IteratorResultsPromises(直接与IteratorResults对比)。
也许同步迭代器能够为每一个可遍历元素返回一个 Promise? 但是, 无论迭代是否完成通常都是异步确定的。

Async iteration 的接口

在 TypeScript 中, 接口看起来就像这样
1
interface AsyncIterable {
2
[Symbol.asyncIterator]() : AsyncIterator;
3
}
4
interface AsyncIterator {
5
next() : Promise<IteratorResult>;
6
}
7
interface IteratorResult {
8
value: any;
9
done: boolean;
10
}
Copied!

for-await-of

async 函数中,可以通过 for-await-of 便利异步便利器。
1
async function f() {
2
for await (const x of createAsyncIterable(['a', 'b'])) {
3
console.log(x);
4
}
5
}
6
// Output:
7
// a
8
// b
Copied!
await 如何在异步函数中工作类似,如果 next() 返回拒绝,则循环抛出异常:
1
function createRejectingIterable() {
2
return {
3
[Symbol.asyncIterator]() {
4
return this;
5
},
6
next() {
7
return Promise.reject(new Error('Problem!'));
8
},
9
};
10
}
11
(async function () { // (A)
12
try {
13
for await (const x of createRejectingIterable()) {
14
console.log(x);
15
}
16
} catch (e) {
17
console.error(e);
18
// Error: Problem!
19
}
20
})(); // (B)
Copied!
for-of-await 在模块和脚本的顶层不起作用。
如果 for-of-await 遍历一个同步遍历器,会将同步便利器转换为异步便利器处理。
1
async function main() {
2
const syncIterable = [
3
Promise.resolve('a'),
4
Promise.resolve('b'),
5
];
6
for await (const x of syncIterable) {
7
console.log(x);
8
}
9
}
10
main();
11
12
// Output:
13
// a
14
// b
Copied!

Asynchronous generators

普通(同步)生成器有助于实现同步迭代。 异步生成器对异步迭代执行相同的操作。
1
async function* createAsyncIterable(syncIterable) {
2
for (const elem of syncIterable) {
3
yield elem;
4
}
5
}
Copied!
通过在函数后面加一个星号将正常函数转换为普通生成器。异步函数通过执行相同操作变为异步生成器。
Asynchronous generators 工作原理:
普通生成器返回生成器对象 genObj。 每次调用 genObj.next()都会返回一个包含生成值的对象{value,done}。 异步生成器返回生成器对象 genObj。 每次调用 genObj.next() 都会返回一个resolve状态,并且值为对象 {value,done}Promise

字符串模版功能修订

带标签的模版字符串应该允许嵌套支持常见转义序列的语言,移除对ECMAScript在带标签的模版字符串中转义序列的语法限制
使用标记的模板文字,您可以通过在模板文字之前提及函数来进行函数调用:
1
String.raw`\u{4B}`; // '\\u{4B}'
Copied!
String.raw 是一个所谓的标记函数。 标记函数在模板文字中接收两个版本的固定字符串片段(模板字符串):
  • Cooked: 转义序列被解析。 \u{4B} => 'K'.
  • Raw: 转义称普通文本. \u{4B} => '\u{4B}'.
1
function tagFunc(tmplObj, substs) {
2
return {
3
Cooked: tmplObj,
4
Raw: tmplObj.raw,
5
};
6
}
7
tagFunc`\u{4B}`; // { Cooked: [ 'K' ], Raw: [ '\\u{4B}' ] }
Copied!
非法转义序列在"cooked"当中仍然会体现出来。它们将以 undefined 元素的形式存在于"cooked"之中:

正则表达式功能调整

ES2018 为该 RegExp 对象增加了四个新功能,进一步提高了 JavaScript 的字符串处理能力。

s (dotAll) flag for regular expressions

正则表达式中的点(.) 存在两个限制。
不能匹配星芒(非 BMP)字符,例如 emoji
星芒字符(astral characters)。 non-BMP 字符中的一种
1
/^.$/.test('😀') // false
Copied!
这个问题可以通过 /u 标志 (unicode 模式) 解决
1
/^.$/u.test('😀') // true
Copied!
与行终止符不匹配
以下字符被 ECMAScript 视为行终止符: U+000A LINE FEED (LF) (\n) U+000D CARRIAGE RETURN (CR) (\r) U+2028 LINE SEPARATOR U+2029 PARAGRAPH SEPARATOR
还有一些 newline-ish 字符不被 ECMAScript 视为行终止符: U+000B VERTICAL TAB (\v) U+000C FORM FEED (\f) U+0085 NEXT LINE
1
/^.$/.test('\n'); // false
Copied!
之前通过以下方式解决
1
/^[^]$/.test('\n'); // true
2
/^[\s\S]$/.test('\n'); // true
3
/^[\d\D]$/.test('\n'); // true
Copied!
ES2018 采用了以下提议用于解决上诉问题,单行模式中(.)能够匹配换行符(\n)
1
/^.$/s.test('\n'); // false
Copied!

RegExp named capture groups

支持在正则表达式中使用(?<name>...)语法命名捕获组
before:
1
const re = /(\d{4})-(\d{2})-(\d{2})/;
2
const match= re.exec('2019-01-10');
3
4
console.log(match[0]); // → 2019-01-10
5
console.log(match[1]); // → 2019
6
console.log(match[2]); // → 01
7
console.log(match[3]); // → 10
Copied!
now:
1
const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
2
const match = re.exec('2019-01-10');
3
4
console.log(match.groups); // → {year: "2019", month: "01", day: "10"}
5
console.log(match.groups.year); // → 2019
6
console.log(match.groups.month); // → 01
7
console.log(match.groups.day); // → 10
Copied!
要将命名的捕获组插入到方法的替换字符串中replace(),您需要使用lt;name>构造。
1
const re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
2
'2019-01-10'.replace(re, 'lt;year>-02-lt;day>') // 2019-02-01
Copied!
正则表达式中的 \k <name> 表示:匹配先前由命名的捕获组名称匹配的字符串。 例如:
1
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/;
2
RE_TWICE.test('abc!abc'); // true
3
RE_TWICE.test('abc!ab'); // false
Copied!

RegExp Unicode property escapes

ES2018 提供了一种称为 Unicode 属性转义的新类型转义序列,它在正则表达式中提供对完整 Unicode 的支持。
Unicode property escapes look like this:
  • Match all characters whose property prop has the value value:
    \p{prop=value}
  • Match all characters that do not have a property prop whose value is value:
    \P{prop=value}
  • Match all characters whose binary property bin_prop is True:
    \p{bin_prop}
  • Match all characters whose binary property bin_prop is False:
    \P{bin_prop}
假设您要在字符串中匹配 Unicode 字符。虽然被认为是一个数字,但是你不能将它与 \d 速记字符类匹配,因为它只支持 ASCII[0-9] 字符。另一方面,Unicode 属性转义可用于匹配 Unicode中 的任何十进制数
1
console.log(/\d/u.test('㉛')); // false
2
3
console.log(/\p{Number}/u.test('㉛')); // true
4
/^\p{White_Space}+$/u.test('\t \n\r'); // true
5
/^\p{Script=Greek}+$/u.test('μετά'); // ture
Copied!

RegExp lookbehind assertions

JavaScript 以前只支持超前断言,现在能够支持后向断言(?<=...)
1
'$foo %foo foo'.replace(/(?<=\$)foo/g, 'bar'); // '$bar %foo foo'
Copied!