for await of
- 我们知道 for…of 是同步运行的,有时候一些任务集合是异步的,那这种遍历怎么办呢?
function Gen (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(time)
}, time)
})
}
async function test () {
let arr = [Gen(2000), Gen(100), Gen(3000)]
for (let item of arr) {
console.log(Date.now(), item.then(console.log))
}
}
test()
// 1560090138232 Promise {<pending>}
// 1560090138234 Promise {<pending>}
// 1560090138235 Promise {<pending>}
// 100
// 2000
// 3000
这里写了几个小任务,分别是 2000ms 、100ms、3000ms后任务结束。在上述遍历的过程中可以看到三个任务是同步启动的,然后输出上也不是按任务的执行顺序输出的,这显然不太符合我们的要求。
聪明的同学一定能想起来 await 的作用,它可以中断程序的执行直到这个 Promise 对象的状态发生改变,我们修改上面的代码:
function Gen (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(time)
}, time)
})
}
async function test () {
let arr = [Gen(2000), Gen(100), Gen(3000)]
for (let item of arr) {
console.log(Date.now(), await item.then(console.log))
}
}
test()
// 2000
// 1560091834772 undefined
// 100
// 1560091836774 undefined
// 3000
// 1560091836775 undefined
- 从返回值看确实是按照任务的先后顺序进行的,其中原理也有说明是利用了 await 中断程序的功能。不过,在 ES9 中也可以用 for…await…of 的语法来操作:
function Gen (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(time)
}, time)
})
}
async function test () {
let arr = [Gen(2000), Gen(100), Gen(3000)]
for await (let item of arr) {
console.log(Date.now(), item)
}
}
test()
// 1560092345730 2000
// 1560092345730 100
// 1560092346336 3000
- 从这个结果来看和第二种写法效果差不多,但是工作原理确完全不同,重点观察下输出的时间(Chrome Console), 第二种写法是代码块中有 await 导致等待 Promise 的状态而不再继续执行;第三种写法是整个代码块都不执行,等待 arr 当前的值(Promise状态)发生变化之后,才执行代码块的内容。
- 回想我们之前给数据结构自定义遍历器是同步的,如果想定义适合 for…await…of 的异步遍历器该怎么做呢?答案是 Symbol.asyncIterator。
let obj = {
count: 0,
Gen (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve({ done: false, value: time })
}, time)
})
},
[Symbol.asyncIterator] () {
let self = this
return {
next () {
self.count++
if (self.count < 4) {
return self.Gen(Math.random() * 1000)
} else {
return Promise.resolve({
done: true,
value: ''
})
}
}
}
}
}
async function test () {
for await (let item of obj) {
console.log(Date.now(), item)
}
}
// 1560093560200 649.3946561938179
// 1560093560828 624.6310222512955
// 1560093561733 901.9497480464518
Promise.prototype.finally()
- Promise.prototype.finally() 方法返回一个Promise,在promise执行结束时,无论结果是fulfilled或者是rejected,在执行then()和catch()后,都会执行finally指定的回调函数。这为指定执行完promise后,无论结果是fulfilled还是rejected都需要执行的代码提供了一种方式,避免同样的语句需要在then()和catch()中各写一次的情况。
- 基本语法
p.finally(onFinally);
p.finally(function() {
// 返回状态为(resolved 或 rejected)
});参数
示例
let connection;
db.open()
.then(conn => {
connection = conn;
return connection.select({ name: 'Jane' });
})
.then(result => {
// Process result
// Use `connection` to make more queries
})
···
.catch(error => {
// handle errors
})
.finally(() => {
connection.close();
});
Object Rest Spread
- 前面在讲 function 的 Rest & Spread 方法,忘却的同学可以去复习下。在 ES9 新增 Object 的 Rest & Spread 方法,直接看下示例:
const input = {
a: 1,
b: 2
}
const output = {
...input,
c: 3
}
console.log(output) // {a: 1, b: 2, c: 3}
这块代码展示了 spread 语法,可以把 input 对象的数据都拓展到 output 对象,这个功能很实用。
我们再来看下 Object rest 的示例:
const input = {
a: 1,
b: 2,
c: 3
}
let { a, ...rest } = input
console.log(a, rest) // 1 {b: 2, c: 3}
- 当对象 key-value 不确定的时候,把必选的 key 赋值给变量,用一个变量收敛其他可选的 key 数据,这在之前是做不到的。
RegExp Updates
- 正则表达式中,点(.)是一个特殊字符,代表任意的单个字符,但是有两个例外。一个是四个字节的 UTF-16 字符,这个可以用u修饰符解决;另一个是行终止符(line terminator character)。
U+000A 换行符(\n)
U+000D 回车符(\r)
U+2028 行分隔符(line separator)
U+2029 段分隔符(paragraph separator)
console.log(/foo.bar/.test('foo\nbar')) // false
console.log(/foo.bar/s.test('foo\nbar')) // true
在 ES5 中我们都是这么解决的:
console.log(/foo[^]bar/.test('foo\nbar')) // true
// or
console.log(/foo[\s\S]bar/.test('foo\nbar')) // true
- 那如何判断当前正则是否使用了 dotAll 模式呢?
const re = /foo.bar/s // Or, `const re = new RegExp('foo.bar', 's');`.
console.log(re.test('foo\nbar')) // true
console.log(re.dotAll) // true
console.log(re.flags) // 's'
- 记住一句话就可以理解 dotAll 模式:它让 . 名副其实。
- (2)named capture groups
我们在写正则表达式的时候,可以把一部分用()包裹起来,被包裹起来的这部分称作“分组捕获”。
console.log('2019-06-07'.match(/(\d{4})-(\d{2})-(\d{2})/))
// ["2019-06-07", "2019", "06", "07", index: 0, input: "2019-06-07", groups: undefined]
- 这个正则匹配很简单,按照 match 的语法,没有使用 g 标识符,所以返回值第一个数值是正则表达式的完整匹配,接下来的第二个值到第四个值是分组匹配(2019,06,07)。
- 此外 match 返回值还有几个属性,分别是 index、input、groups。
index [匹配的结果的开始位置]
input [搜索的字符串]
groups [一个捕获组数组 或 undefined(如果没有定义命名捕获组)]
我们通过数组来获取这些捕获:
let t = '2019-06-07'.match(/(\d{4})-(\d{2})-(\d{2})/)
console.log(t[1]) // 2019
console.log(t[2]) // 06
console.log(t[3]) // 07
- 上文中重点看下 groups 的解释,这里提到了命名捕获组的概念,如果没有定义 groups 就是 undefined。很明显,我们上述的返回值就是 undefined 间接说明没有定义命名捕获分组。那什么是命名捕获分组呢?
console.log('2019-06-07'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/))
// ["2019-06-07", "2019", "06", "07", index: 0, input: "2019-06-07", groups: {…}]
- 这段代码的返回值 groups 已经是 Object 了,具体的值是:
groups: {year: "2019", month: "06", day: "07"}
- 这个 Object 的 key 就是正则表达式中定义的,也就是把捕获分组进行了命名。想获取这些捕获可以这样做:
let t = '2019-06-07'.match(/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/)
// ["2019-06-07", "2019", "06", "07", index: 0, input: "2019-06-07", groups: {…}]
console.log(t.groups.year) // 2019
console.log(t.groups.month) // 06
console.log(t.groups.day) // 07
(3)Lookbehind Assertions
- 在 ES9 之前 JavaScript 正则只支持先行断言,不支持后行断言。简单复习下先行断言的知识:
let test = 'hello world'
console.log(test.match(/hello(?=\sworld)/))
// ["hello", index: 0, input: "hello world", groups: undefined]
这段代码要匹配后面是 world 的 hello,但是反过来就不成:
let test = 'world hello'
console.log(test.match(/hello(?=\sworld)/))
// null
比如我们想判断前面是 world 的 hello,这个代码是实现不了的。在 ES9 就支持这个后行断言了:
let test = 'world hello'
console.log(test.match(/(?<=world\s)hello/))
// ["hello", index: 6, input: "world hello", groups: undefined]
(?<…)是后行断言的符号,(?..)是先行断言的符号,然后结合 =(等于)、!(不等)、\1(捕获匹配)。
- Unicode Property Escapes
- 了解这个新的知识点,需要对文本的编码非常熟悉,不然意识不到这个功能的意义。对于文本的编码需要了解两个概念:字符编码和文件编码。字符编码包括 ASCII 和 Unicode,文件编码包括 UTF-8、GBK等。字符编码和文件编码的关系可以用一句话来概括:文件编码和字符编码没有关系,也就是说即使指定了文件编码,字符变也可以灵活选择而不受任何限制。
- 现在主要讲述下 Unicode 的知识点,方便快速了解 ES9 这个新特性。根据 Unicode 规范,每一个 Unicode 字符除了有唯一的码点,还具有其它属性,它们是:Unicode Property、Unicode Block、Unicode Script,下面的图粗略说明了这三者的关系。
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", "中", "国"]
- 这段代码的含义是在输入中匹配所有的字符(不限语言),这里使用的是 Unicode Property:{L},这个属性的含义是任何语言的任何字母。它有点等同于
let input = 'abcdAeCd中国'
console.log(input.match(/./sg))
{Ll} [任何具有大写字母的小写字母]
{N} [任何语言下的数字]
- 按照字符所属的书写系统来划分字符,它一般对应某种语言。比如 \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 之前大概只能这样做:
let input = `I'm chinese!我是中国人`
console.log(input.match(/[\u4e00-\u9fa5]+/))
// ["我是中国人", index: 12, input: "I'm chinese!我是中国人", groups: undefined]
- 虽然不同的写法看上去结果一样,然而时光飞逝,Unicode 在2017年6月发布了10.0.0版本。在这20年间,Unicode 添加了许多汉字。比如 Unicode 8.0 添加的 109 号化学元素「鿏(⿰⻐麦)」,其码点是 9FCF,不在这个正则表达式范围中。而如果我们期望程序里的/[\u4e00-\u9fa5]/可以与时俱进匹配最新的 Unicode 标准,显然是不现实的事情。现在只需要在 Unicode Scripts 找到对应的名称即可,而不需要自己去计算所有对应语言字符的的 Unicode 范围。
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
[!WARNING]
目前 JavaScript RegExp 还不支持 Unicode Block