ES2018重点总结
1.for await of
(1)for await of和for…of的对比及使用
for…of循环用于遍历同步的 Iterator 接口,for await…of循环用于遍历异步的 Iterator 接口。
function Gen (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve(time)
}, time)
})
}
// for ... of
async function test1 () {
let arr = [Gen(2000), Gen(100), Gen(3000)]
for (let item of arr) {
console.log(Date.now(), await item.then(console.log))
}
}
test1()
// 2000
// 1616227047645 undefined
// 100
// 1616227049650 undefined
// 3000
// 1616227049651 undefined
// for await of
async function test () {
let arr = [Gen(2000), Gen(100), Gen(3000)]
for await (let item of arr) {
console.log(Date.now(), item)
}
}
test()
// 1616225851505 2000
// 1616225851506 100
// 1616225852505 3000
(2)数据结构自定义异步遍历器
自定义异步遍历器其实和自定义同步遍历器有很多相似之处:
异步可迭代协议
如果让一个对象是可遍历的,就要遵守异步可迭代协议,该协议要求
1.对象要部署一个以 Symbol.asyncIterator为 key 的键值对
(同步实现的是Symbol.iterator)
2. value 就是一个要遵守迭代器协议的无参函数
迭代器协议
迭代器协议要求符合以下条件:
1.一个对象需包含一个无参函数 next
2.next 返回值也是一个对象,包含 done 和 value 属性。
其中 done 表示遍历是否结束,value 返回当前遍历的值。
2.Promise.prototype.finally()
finally()方法用于指定不管 Promise 对象最后状态如何,在执行完then或catch指定的回调函数以后,都会执行的操作。常用于关闭数据库连接等操作。
finally方法的回调函数不接受任何参数,finally方法里面的操作,应该是与状态无关的,不依赖于 Promise 的执行结果。
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});
3.对象的扩展运算符 …
(1)解构赋值
解构赋值用于从一个对象取值,相当于将目标对象自身的所有可遍历的(enumerable)、但尚未被读取的属性,分配到指定的对象上面。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
(2)扩展运算符
对象的扩展运算符(…)用于取出参数对象的所有可遍历属性,拷贝到当前对象之中。
const input = {
a: 1,
b: 2
}
const output = {
...input,
c: 3
}
console.log(output) // {a: 1, b: 2, c: 3}
4.正则的扩展
(1)s 修饰符:dotAll 模式
正则表达式中,点(.)是一个特殊字符,代表任意的单个字符,但是有两个例外。
一个是四个字节的 UTF-16 字符,这个可以用u修饰符解决;
另一个是行终止符(line terminator character)。
所谓行终止符,就是该字符表示一行的终结。以下四个字符属于“行终止符”。
U+000A 换行符(\n)
U+000D 回车符(\r)
U+2028 行分隔符(line separator)
U+2029 段分隔符(paragraph separator)
(1)字符
s修饰符,使得.可以匹配任意单个字符。 这被称为dotAll模式,即点(dot)代表一切字符。
foo.bar/.test('foo\nbar') // false
/foo.bar/s.test('foo\nbar') // true
(2)属性
正则表达式还引入了一个dotAll属性,返回一个布尔值,表示该正则表达式是否处在dotAll模式。
const re = /foo.bar/s;
re.test('foo\nbar') // true
re.dotAll // true
re.flags // 's'
(2)具名组匹配 named capture groups
// match
const str = '2021-03-20'
const reg = /(\d{4})-(\d{2})-(\d{2})/
console.log(str.match(reg))
// ["2021-03-20", "2021", "03", "20", index: 0, input: "2021-03-20", groups: undefined]
// index [匹配的结果的开始位置]
// input [搜索的字符串]
// groups [一个捕获组数组 或 undefined(如果没有定义命名捕获组)]
// exec
const matchObj = reg.exec(str);
const year = matchObj[1]; // 2021
const month = matchObj[2]; // 03
const day = matchObj[3]; // 20
// 组匹配的一个问题是,每一组的匹配含义不容易看出来,而且只能用数字序号
//(比如matchObj[1])引用,要是组的顺序变了,引用的时候就必须修改序号。
// 具名组匹配(Named Capture Groups),允许为每一个组匹配指定一个名字,既便于阅读代码,又便于引用
const REG = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const matchObj = REG.exec('2021-03-20');
const year = matchObj.groups.year; // "2021"
const month = matchObj.groups.month; // "03"
const day = matchObj.groups.day; // "20"
// “具名组匹配”在圆括号内部,模式的头部添加“问号 + 尖括号 + 组名”
// (如?<year>),然后就可以在exec方法返回结果的groups属性上引用该组名。
// 同时,数字序号(matchObj[1])依然有效。
let t = '2021-03-20'.match(REG)
console.log(t.groups.year) // 2021
console.log(t.groups.month) // 03
console.log(t.groups.day) // 20
(3)后行断言
在 ES2018 之前 JavaScript 正则只支持先行断言,不支持后行断言。
(?<…)是后行断言的符号,(?..)是先行断言的符号然后结合 =(等于)、!(不等)、\1(捕获匹配)。
先行断言&先行否定断言
“先行断言”指的是,x只有在y前面才匹配,必须写成/x(?=y)/
“先行否定断言”指的是,x只有不在y前面才匹配,必须写成/x(?!y)/。
let test = 'hello123helloworld'
// 先行断言:匹配hello,但其后面必须是123
console.log(test.match(/hello(?=123)/))
// ["hello", index: 0, input: "hello123helloworld", groups: undefined]
// 先行否定断言:匹配hello,但后面必须不是123
console.log(test.match(/hello(?!123)/))
// ["hello", index: 8, input: "hello123helloworld", groups: undefined]
后行断言&后行否定断言
“后行断言”正好与“先行断言”相反,x只有在y后面才匹配,必须写成/(?<=y)x/。
“后行否定断言”则与“先行否定断言”相反,x只有不在y后面才匹配,必须写成/(?<!y)x/。
let test = 'hello123helloworld'
// 后行断言:匹配hello,但前面必须是123
console.log(test.match(/(?<=123)hello/))
// ["hello", index: 8, input: "hello123helloworld", groups: undefined]
// 后行否定断言:匹配hello,但前面必须不是123
console.log(test.match(/(?<!123)hello/))
// ["hello", index: 0, input: "hello123helloworld", groups: undefined]
(4)Unicode 属性类
Unicode 属性类\p{key=value}
要指定属性名和属性值,从而允许正则表达式匹配一类字符组。(对于某些属性,可以只写属性名,或者只写属性值。)
根据 Unicode 规范,每一个 Unicode 字符除了有唯一的码点,还具有其它属性它们是:Unicode Property、Unicode Block、Unicode Script。
\P{…}是\p{…}的反向匹配,即匹配不满足条件的字符。
这两种类只对 Unicode 有效,所以使用的时候一定要加上u修饰符。如果不加u修饰符,正则表达式使用\p和\P会报错。
例如:\p{Script=Greek}指定匹配希腊文字母 或 匹配所有数字
// 匹配希腊文字母
const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π') // true
// 匹配所有数字
const regex = /^\p{Number}+$/u;
regex.test('²³¹¼½¾') // true
regex.test('㉛㉜㉝') // true
regex.test('ⅠⅡⅢⅣⅤⅥⅦⅧⅨⅩⅪⅫ') // true
字符编码包括 ASCII 和 Unicode,文件编码包括 UTF-8、GBK等。
文件编码和字符编码没有关系,即使指定了文件编码,字符变也可以灵活选择而不受任何限制。
Unicode Property
按照字符的功能对字符进行分类,一个字符只能属于一个 Unicode Property。
可以将Unicode property 理解为字符组,在正则中用 /p{属性类型} 表示。
例如p属性的字符组和P属性的字符组,将小写 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))
// ["a", "b", "c", "d", "A", "e", "C", "d", "中", "国"]
// 其它类型:
// {Ll} [任何具有大写字母的小写字母]
// {N} [任何语言下的数字]
Unicode Script
按照字符所属的书写系统来划分字符,它一般对应某种语言。比如 \p{Script=Greek} 表示希腊语,\p{Script=Han} 表示汉语。
我们只需要在 Unicode Scripts 找到对应的名称即可,而不需要自己去计算所有对应语言字符的的 Unicode 范围。
before:
let input = `I'm chinese!我是中国人`
console.log(input.match(/[\u4e00-\u9fa5]+/))
// ["我是中国人", index: 12, input: "I'm chinese!我是中国人", groups: undefined]
after:
let input = `I'm chinese!我是中国人`
console.log(input.match(/\p{Script=Han}+/u))
// ["我是中国人", index: 12, input: "I'm chinese!我是中国人", groups: undefined]
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