ES9
ECMAScript 9(简称ES9)是于2018年6月正式发布的JavaScript语言的标准,正式名为ECMAScript 2018(ES2018)。
# Promise.prototype.finally
方法返回一个Promise。在promise结束时,无论结果是fulfilled或者是rejected,都会执行指定的回调函数。这为在Promise是否成功完成后都需要执行的代码提供了一种方式。这避免了同样的语句需要在then()和catch()中各写一次的情况。
语法:
p.finally(onFinally);
p.finally(function() {
// 返回状态为(resolved 或 rejected)
});
2
3
4
5
参数:
- onFinally:Promise 结束后调用的Function。
返回值:返回一个设置了 finally 回调函数的Promise对象。
finally() 虽然与 .then(onFinally, onFinally) 类似,它们不同的是:
- 调用内联函数时,不需要多次声明该函数或为该函数创建一个变量保存它。
- 由于无法知道promise的最终状态,所以finally的回调函数中不接收任何参数,它仅用于无论最终结果如何都要执行的情况。
Promise.resolve()
.then(result => {
console.log('我执行了')
})
.catch(error => {})
.finally(() => {
console.log('我会始终执行')
});
2
3
4
5
6
7
8
相关信息
在finally回调中 throw(或返回被拒绝的promise)将以 throw() 指定的原因拒绝新的promise.
# Asynchronous Iteration
此特性添加了一个新的“for-await-of”循环,允许我们在循环中调用返回 promises(或带有一堆 promise 的 Arrays )的异步函数。 循环会等待每个 Promise 在进行下一个循环之前 resolve 。
const promises = [
new Promise(resolve => {
setTimeout(() => {
console.log(1)
resolve(1)
}, 5000)
}),
new Promise(resolve => {
setTimeout(() => {
console.log(2)
resolve(2)
}, 3000)
}),
new Promise(resolve => {
setTimeout(() => {
console.log(3)
resolve(3)
}, 3000)
}),
]
async function test2() {
// await Promise.all(promises)
// console.log('end2')
for await (const res of promises) {
console.log(res, '--')
}
}
test2();
console.log('end')
/**
* end
* 2
* 3
* 1
* 1 --
* 2 --
* 3 --
*/
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
在这里我其实有一个疑问,for-await-of确实可以让当前结果resole之后,再执行下一个循环,但是我没发现这和promise.all有什么区别。
# RegExp Unicode Property Escapes
了解这个新的知识点,需要对文本的编码非常熟悉,不然意识不到这个功能的意义。对于文本的编码需要了解两个概念:字符编码和文件编码。
- 字符编码包括 ASCII 和 Unicode
- 文件编码包括 UTF-8、GBK等。
文件编码和字符编码没有关系,也就是说即使指定了文件编码,字符变也可以灵活选择而不受任何限制。根据 Unicode 规范,每一个 Unicode 字符除了有唯一的码点,还具有其它属性:
- Unicode Property
- Unicode Script
- Unicode Block
这些一般用在正则里面。
# Unicode Property
过去ECMAScript 的原生RegExp 无法直接存取有些Unicode 的字元property,简化来说就是原生RegExp 无法完整支援Unicode。因为原生未提供,所以只能透过其他library 来解决,但这些方法不是很理想。ES9提供了RegExp 的Unicode property escapes,语法为 \p{...} 和\P{...},而其中的 p 代表的是「property」的意思。Unicode property escapes 是一种新的escape sequence (跳脱序列),可用于设定uflag 的RegExp,这样可以更好的表示 \p{...} 内的表达式与Unicode property 有关。此提案解决了过去的问题,因为已变为原生支援,无须使用额外的library。\P{...}是 \p{...} 的反向形式(negated form),就跟 \d 代表数字字元,\D代表非数字字元一样,大写字有「negated」的意思。
Unicode Property按照字符的功能对字符进行分类,一个字符只能属于一个Unicode Property。也就是说 Property 并不关心字符所属的语言,只关心字符的功能。
可以将Unicode property 理解为字符组,将小写 p 改成大写,就是该字符组的排除型字符组。想想看 \d 匹配 0-9 这个字符组,而 \D 匹配 0-9 以外的字符组。
let input = 'abcdAeCd中国'
console.log(input.match(/\p{L}/ug)); // ["a", "b", "c", "d", "A", "e", "C", "d", "中", "国"]
2
3
这段代码的含义是在输入中匹配所有的字符(不限语言),这里使用的是 Unicode Property:{L},这个属性的含义是任何语言的任何字母。
# Unicode Script
按照字符所属的书写系统来划分字符,它一般对应某种语言。比如 \p{Script=Greek} 表示希腊语,\p{Script=Han} 表示汉语。
// 匹配下列字符串中的中文
let input = `I'm chinese!我是中国人`
console.log(input.match(/\p{Script=Han}+/u)); // [ '我是中国人', index: 12, input: "I'm chinese!我是中国人", groups: undefined ]
// es9之前
console.log(input.match(/[\u4e00-\u9fa5]+/))
// π 字符是一个希腊字符,通过指定 \p{Script=Greek} 就可以匹配这个字符了
const regexGreekSymbol = /\p{Script=Greek}/u;
console.log(regexGreekSymbol.test('π')); // true
2
3
4
5
6
7
8
9
10
11
12
相关信息
\u4e00和\u9fa5
是unicode编码,并且正好是中文编码的开始和结束的两个值,所以可以使用这个正则表达式来判断字符串中是否包含中文。- 虽然不同的写法看上去结果一样,然而时光飞逝,Unicode 在2017年6月发布了10.0.0版本。在这20年间,Unicode 添加了许多汉字。比如 Unicode 8.0 添加的 109 号化学元素「鿏(⿰⻐麦)」,其码点是 9FCF,不在这个正则表达式范围中。而如果我们期望程序里的/[\u4e00-\u9fa5]/可以与时俱进匹配最新的 Unicode 标准,显然是不现实的事情。现在只需要在 Unicode Scripts 找到对应的名称即可,而不需要自己去计算所有对应语言字符的的 Unicode 范围。
- 虽然可以通过引用希腊字符(或者其他编码)表做正则处理,当每当更新表时,更新起来会非常麻烦,不如让浏览器原生支持 \p{UnicodePropertyName=UnicodePropertyValue} 的正则语法,帮助开发人员解决这个烦恼。
# Unicode Block
将 Unicode 字符按照编码区间进行划分,所以每一个字符都只属于一个 Unicode Block。
示例:
\p{InBasic_Latin}
:U+0000–U+007F
\p{InLatin-1_Supplement}
:U+0080–U+00FF
\p{InLatin_Extended-A}
:U+0100–U+017F
\p{InLatin_Extended-B}
:U+0180–U+024F
::: warnign 目前 JavaScript RegExp 还不支持 Unicode Block :::
# RegExp Lookbehind Assertions
这个是对正则表达式的增强。javascript 正则表达式一直不支持后行断言,后向断言会获取某个字符后面跟的内容,在获取美刀等货币单位上有很大用途。
# 前行断言
// 它匹配字符串'aabb',但整体匹配的字符串不包括 b。
const RE_AS_BS = /aa(?=bb)/;
const match1 = RE_AS_BS.exec('aabb');
console.log(match1[0]); // 'aa'
// 此外,它不匹配没有两个 b 的字符串
const match2 = RE_AS_BS.exec('aab');
console.log(match2); // null
// 否定的前行断言
const RE_AS_NO_BS = /aa(?!bb)/;
RE_AS_NO_BS.test('aabb'); // false
RE_AS_NO_BS.test('aab'); // true
RE_AS_NO_BS.test('aac'); // true
2
3
4
5
6
7
8
9
10
11
12
13
14
# 后行断言
// 'foo'仅当它前面有美元符号时才被替换。
const RE_DOLLAR_PREFIX = /(?<=\$)foo/g;
const str = '$foo %foo foo'.replace(RE_DOLLAR_PREFIX, 'bar');
console.log(str); // $bar %foo foo
// 没有后向断言的情况下实现相同的结果
const RE_DOLLAR_PREFIX = /(\$)foo/g;
const str = '$foo %foo foo'.replace(RE_DOLLAR_PREFIX, '$1bar');
console.log(str); // $bar NaNoo foo
// 否定的后行断言
const RE_NO_DOLLAR_PREFIX = /(?<!\$)foo/g;
const str = '$foo %foo foo'.replace(RE_NO_DOLLAR_PREFIX, 'bar');
console.log(str); // $foo %bar bar
2
3
4
5
6
7
8
9
10
11
12
13
14
# Rest/Spread Properties
Rest/Spread在ES6中已经引入,不过es6中只针对于数组。在ES9中为对象提供了像数组一样的REST参数和扩展运算符。
Sebastian Markbåge 提出的 Rest/Spread Properties 提案包括两部分:
- 用于对象解构的 rest 操作符(...)。目前,这个操作符只能在数组解构和参数定义中使用。
- 对象字面量中的 spread 操作符(...)。目前,这个操作符只能用于数组字面量和在函数方法中使用。
# Spread(展开语法)
语法(Spread syntax, 可以在使用数组类型时) 将表达式展开在函数表达式中;还可以在构造字面对象时,按key-value的方式展开。
语法:
- 函数调用
myFunction(...iterableObj);
1 - 字面量字体或字符串
[...iterableObj, '4', ...'hello', 6];
1 - 构造字面量对象或属性复制时
let objClone = { ...obj };
1
// 在函数调用时使用展开语法
function myFunction(x, y, z) { }
var args = [0, 1, 2];
myFunction(...args);
// 构造字面量时
var parts = ['shoulders', 'knees'];
var lyrics = ['head', ...parts, 'and', 'toes'];
var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
var clonedObj = { ...obj1 };
// 克隆后的对象: { foo: "bar", x: 42 }
var mergedObj = { ...obj1, ...obj2 };
// 合并后的对象: { foo: "baz", x: 42, y: 13 }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
相关信息
- 展开运算符属于浅拷贝。
- Object.assign()函数会触发 setters,而展开语法则不会。
- 不能替换或模拟 Object.assign()函数:
var obj1 = { foo: 'bar', x: 42 };
var obj2 = { foo: 'baz', y: 13 };
const merge = ( ...objects ) => ( { ...objects } );
var mergedObj = merge ( obj1, obj2);
// Object { 0: { foo: 'bar', x: 42 }, 1: { foo: 'baz', y: 13 } }
var mergedObj = merge ( {}, obj1, obj2);
// Object { 0: {}, 1: { foo: 'bar', x: 42 }, 2: { foo: 'baz', y: 13 } }
2
3
4
5
6
7
8
9
# rest(剩余参数语法)
该语法允许我们将一个不定数量的参数表示为一个数组。如果函数的最后一个命名参数以...为前缀,则它将成为一个由剩余参数组成的真数组,其中从0(包括)到theArgs.length(排除)的元素由传递给函数的实际参数提供。
语法:
function(a, b, ...theArgs) {
// ...
}
2
3
function main(a, ...rest) {
console.log(a); // 1
console.log(rest); // [2, 3]
}
main(1, 2, 3)
2
3
4
5
6
相关信息
剩余参数和 arguments对象之间的区别主要有三个:
- 剩余参数只包含那些没有对应形参的实参,而 arguments 对象包含了传给函数的所有实参。
- arguments对象不是一个真正的数组,而剩余参数是真正的 Array实例,也就是说你能够在它上面直接使用所有的数组方法,比如 sort,map,forEach或pop。
- arguments对象还有一些附加的属性 (如callee属性)。
注意
在每个对象字面量的顶层,可以使用 rest 操作符最多一次,并且必须只能在末尾出现:
const {...rest, foo} = obj; // SyntaxError
const {foo, ...rest1, ...rest2} = obj; // SyntaxError
2
# RegExp named capture groups
可以使用(?
...)语法为捕获组指定名称name。日期的正则表达式可以写成/(? \d{4})-(? \d{2})-(? \d{2})/u。每个名称都应该是唯一的,并遵循 ECMAScript IdentifierName 的语法。可以从groups正则表达式结果的属性的属性中访问命名组。与未命名的组一样,也会创建对组的编号引用。
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result = re.exec('2015-01-02');
console.log(result);
/**
* [
* '2015-01-02',
* '2015',
* '01',
* '02',
* index: 0,
* input: '2015-01-02',
* groups: { year: '2015', month: '01', day: '02' }
* ]
*/
// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';
// result[0] === '2015-01-02';
// result[1] === '2015';
// result[2] === '01';
// result[3] === '02';
// 解构获取组名
let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
console.log(`one: ${one}, two: ${two}`); // one: foo, two: bar
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# s (dotAll) flag for regular expressions
在正则表达式模式中,我们可以使用点 .
匹配任意的单个字符。但是这里面却有两个例外,默认:
.
不匹配 星体字符.
不匹配 行终止符
默认不匹配 星体字符,我们可以通过给正则设置 u (unicode) 这个标志来解决这个问题。但是对于 行终止符 来说,却没有一个类似的 flag 来解决这个问题。按照正常的正则语义,点 . 可以匹配任意字符,但实际上只能识别下面列出的 行终止符 :
- U+000A 换行 (LF) (\n)
- U+000D 回车 (CR) (\r)
- U+2028 线分隔符
- U+2029 段落分隔符
在实际的场景中,还有一些字符可以被认为是 行终止符 ,比如说:
- U+000B 垂直制表符 (\v)
- U+000C 换页 (\f)
- U+0085 下一行
所以 es9 新增了一个 flag s,来补充上面的场景。
const str = `hello\nworld`;
const r1 = /hello.world/;
console.log(r1.test(str), r1.dotAll, r1.flags); // false false
// 添加 's' flag
const r2 = /hello.world/s;
console.log(r2.test(str), r2.dotAll, r2.flags); // true true s
2
3
4
5
6
7
# Lifting template literal restriction
放松对标签模板里面的字符串转义的限制。如果遇到不合法的字符串转义,就返回undefined,而不是报错,并且从raw属性上面可以得到原始字符串。
Template literals遵从字符串的转义规则:
- 以
\u
开头,后跟4个16进制数字,例如,\u00B1表示± - 以
\u
开头,使用大括号括起来的16进制数字,例如,\u{2F804}表示你 - 以
\x
开头,后跟2个16进制数字,例如,\xB1表示± - 以
\
开头,后跟10进制数字,用来表示八进制字面量(注:strict mode下不支持)
解释器遇到\u和\x,如果发现后面的字符不满足以上条件,就会报语法错。
const str = `\unicode`;
console.log(str, 'Yahweh'); // Invalid Unicode escape sequence
2
3
Lifting template literal restriction改善了这个问题:
- 去除\u或者\x的限制,只适用于Tagged template literals,而对于untagged template literals无效,仍然会报语法错。
- 为了兼容性,该提案只是将转义后的字符串数组strs,置为单元素数组[undefined],tag函数需要手动处理strs.raw进行处理。
const foo = (strs, ...exprs) => {
console.log(strs); // [undefined]
console.log(strs.raw); // ["something else \unicode"]
};
foo`something else \unicode`;
2
3
4
5
6
上面代码中,模板字符串原本是应该报错的,但是由于放松了对字符串转义的限制,所以不报错了,JavaScript 引擎将第一个字符设置为undefined,但是raw属性依然可以得到原始字符串,因此tag函数还是可以对原字符串进行处理。