0%

ES2018

说明

记录 ES2018 相关知识

异步迭代

  • 通过 Symbol.asyncIterator 访问内建的迭代器
  • 方法 next() 返回的是 Promise
  • 统一定义 wait 函数,下面用到
1
2
3
4
5
6
7
function wait(sec) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
resolve(sec)
}, sec * 1000)
})
}

for await…of

1
2
3
4
5
6
7
8
9
10
11
async function doFun() {
for await (const res of [wait(2), wait(4)]) {
console.log(res)
}
}

doFun()
// 2s 后输出
// 2
// 4s 后输出
// 4
  • for-await-of 不能在模块最顶层使用,同 await 要在 async 函数内使用
1
2
3
4
5
6
7
8
9
10
11
async function doFun2() {
for await (const res of [wait(4), wait(2)]) {
console.log(res)
}
}

doFun2()

// 4s 后输出
// 4
// 2
1
2
3
4
5
6
7
8
9
10
11
async function doFun3() {
for (const x of await Promise.all([wait(2), wait(4)])) {
console.log(x)
}
}

doFun3()

// 过 4s 后输出
// 2
// 4

异步 generator 函数

  • 异步 Generator 函数的返回值是一个异步 Iterator,即每次调用它的 next 方法,会返回一个 Promise 对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
async function* asyncGenerator() {
console.log('Start')
const result = await wait(2) // (A)
yield 'Result: ' + result // (B)
console.log('Done')
}

const ag = asyncGenerator()
ag.next().then(({ value, done }) => {
console.log(value)
})

// Start
// 2s 后继续输出
// Result: 2

抛出错误

1
2
3
4
5
6
7
8
async function* asyncGenerator() {
// The following exception is converted to a rejection
throw new Error('Problem!')
}

asyncGenerator()
.next()
.catch(err => console.log(err)) // Error: Problem!

解构

rest 与对象解构

1
2
3
const obj = { foo: 1, bar: 2, baz: 3 }
const { foo, ...rest } = obj
console.log(rest) // { bar: 2, baz: 3 }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const obj = {
foo: {
a: 1,
b: 2,
c: 3
},
bar: 4,
baz: 5
}
const {
foo: { a, ...rest1 },
...rest2
} = obj

console.log(rest1) // { b: 2, c: 3 }
console.log(rest2) // { bar: 4, baz: 5 }
  • 错误情况
1
2
const { ...rest, foo } = obj // SyntaxError
const { foo, ...rest1, ...rest2 } = obj // SyntaxError

对象解构 vs. Object.assign()

  • Object.assign() 触发源对象的 set 方法,解构则不会
1
2
3
4
5
6
Object.defineProperty(Object.prototype, 'foo', {
set(value) {
console.log('SET', value)
}
})
const obj = { foo: 123 }
1
2
Object.assign({}, obj)
// SET 123
1
const res = { ...obj } // 没有输出
  • 对于只读属性
1
2
3
4
5
6
// 前提
'use strict'
Object.defineProperty(Object.prototype, 'bar', {
writable: false,
value: 'abc'
})
1
2
3
const tmp = {}
tmp.bar = 123
// TypeError: Cannot assign to read only property 'bar' of object '#<Object>'
1
const obj = { bar: 123 } // 正常
1
2
3
4
const obj = { bar: 123 }

Object.assign({}, obj)
// TypeError: Cannot assign to read only property 'bar' of object '#<Object>'
1
2
3
4
const obj = { bar: 123 }

const obj2 = { ...obj }
console.log(obj2) // { bar: 123 }
  • 两者都只考虑非原型上的可枚举属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const proto = {
inheritedEnumerable: 1
}
const obj = Object.create(proto, {
ownEnumerable: {
value: 2,
enumerable: true
},
ownNonEnumerable: {
value: 3,
enumerable: false
}
})

const res1 = { ...obj }
console.log(res1) // { ownEnumerable: 2 }

const res2 = Object.assign({}, obj)
console.log(res2) // { ownEnumerable: 2 }

正则表达式

命名捕获组(Named capture groups)

1
2
3
4
5
6
const RE_DATE = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/

const matchObj = RE_DATE.exec('1999-12-31')
const year = matchObj.groups.year // 1999
const month = matchObj.groups.month // 12
const day = matchObj.groups.day // 31
  • Named capture groups 同样可以通过下标获取组
1
2
3
const year2 = matchObj[1] // 1999
const month2 = matchObj[2] // 12
const day2 = matchObj[3] // 31

反向引用(Backreferences)

\k<name> 在正则表达式中表示:匹配先前捕获过的同名捕获组字符串

1
2
3
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>$/
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false

编号捕获组(numbered capture groups )的反向引用语法 也适用于 命名捕获组

1
2
3
const RE_TWICE = /^(?<word>[a-z]+)!\1$/
RE_TWICE.test('abc!abc') // true
RE_TWICE.test('abc!ab') // false

两者也可以混合使用

1
2
3
const RE_TWICE = /^(?<word>[a-z]+)!\k<word>!\1$/
RE_TWICE.test('abc!abc!abc') // true
RE_TWICE.test('abc!abc!ab') // false

replace() 和命名捕获组

1
2
3
const RE_DATE = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/
console.log('1999-12-31'.replace(RE_DATE, '$<month>/$<day>/$<year>'))
// 12/31/1999
1
2
3
4
5
6
7
8
const RE_DATE = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/
console.log(
'1999-12-31'.replace(
RE_DATE,
(g0, y, m, d, offset, input, { year, month, day }) => month + '/' + day + '/' + year
)
)
// 12/31/1999
  • g0 包含匹配得到的整个字符串 1999-12-31
  • y, m, d 指捕获组 1-3,也是命名组 year, month, day
  • offset 指匹配到的内容在字符串中的位置
  • input 指输入的整个字符串
  • 最后的一个参数是新增的,包含命名捕获组,这里是 year, month, day
1
2
3
4
5
6
7
8
9
10
11
// 获取最后一个参数的另一种方法
const RE_DATE = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/

console.log(
'1999-12-31'.replace(RE_DATE, (...args) => {
const { year, month, day } = args[args.length - 1]
return month + '/' + day + '/' + year
})
)

// 12/31/1999

未匹配的命名组

1
2
3
4
5
6
7
8
const RE_OPT_A = /^(?<as>a+)?$/
const matchObj = RE_OPT_A.exec('')

// Group <as> didn’t match anything:
console.log(matchObj.groups.as === undefined) // true

// But property `as` exists:
console.log('as' in matchObj.groups) // true

正则表达式 Unicode 转义属性

正则表达式中使用 u 标志(flags),之后在\p{}花括号内声明 Unicode 字符属性

1
2
3
4
5
6
7
// 匹配空白符
/^\p{White_Space}+$/u.test('\t \n\r')
// true

// 匹配希腊字母
/^\p{Script=Greek}+$/u.test('μετά')
// true

WIKI - Unicode 字符属性

正则表达式 - lookbehind assertion

翻译成:反向断言?回顾断言?翻译成反向断言算了,重在理解。

符号 翻译 含义
(?<=exp)reg 反向肯定断言 reg 匹配的内容前面内容满足 exp 规则
(?<!exp)reg 反向否定断言 reg 匹配的内容前面内容不满足 exp 规则
  • 正向断言(lookahead assertions)
符号 翻译 含义
reg(?=exp) 正向肯定断言 reg 匹配的内容后面内容满足 exp 规则
reg(?!exp) 正向否定断言 reg 匹配的内容后面内容不满足 exp 规则

正向断言和反向断言均不捕获分组。

eg

  1. 正向断言
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 正向肯定断言
const RE_AS_BS = /aa(?=bb)/
const match1 = RE_AS_BS.exec('aabb')
match1[0]
// 'aa'

const match2 = RE_AS_BS.exec('aab')
match2
// null

// 正向否定断言
const RE_AS_NO_BS = /aa(?!bb)/
RE_AS_NO_BS.test('aabb')
// false

RE_AS_NO_BS.test('aab')
// true
  1. 反向断言
1
2
3
4
5
6
7
8
// 反向肯定断言
'a1ba2ba3b'.match(/(?<=b)a.b/g)
// [ 'a2b', 'a3b' ]

// 反向否定断言
const RE_NO_DOLLAR_PREFIX = /(?<!\$)foo/g
'$foo %foo foo'.replace(RE_NO_DOLLAR_PREFIX, 'bar')
// '$foo %bar bar'

正则表达式 s(dotAll) 标志符

  1. 添加s标志符的作用:使正则表达式中的 . 匹配包括行终止符在内的所有单字符。不添加的话不匹配行终止符。
1
2
3
4
5
/^.$/.test('\n')
// false

/^.$/s.test('\n')
// true

行终止符包括\n, \r等。ECMA-line terminators

  1. 标志符 /s对应的正则实例属性是 dotAll
1
2
3
4
5
/./s.dotAll
// true

/./g.global
// true

Promise.finally()

  • then’s callback is only executed if promise is fulfilled
  • catch’s callback is only executed if promise is rejected. Or then’s callback throws an exception or returns a rejected Promise.
  • 不管 fulfilled 或 rejected, finally 都会执行
1
2
3
4
promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···})

模板字符串

带标签的模板字面量及转义序列

  1. 自 ES2016 起,带标签的模版字面量遵守以下转义序列的规则:

    • Unicode 字符以"\u"开头,例如\u00A9
    • Unicode 码位用"\u{}"表示,例如\u{2F804}
    • 十六进制以"\x"开头,例如\xA9
    • 八进制以"\"和数字开头,例如\251
  2. ES2018 关于非法转义序列的修订
    移除对 ECMAScript 在带标签的模版字符串中转义序列的语法限制。不过,非法转义序列在”cooked”当中仍然会体现出来。它们将以 undefined 元素的形式存在于”cooked”之中:

1
2
3
4
5
6
7
function latex(str) {
return { cooked: str[0], raw: str.raw[0] }
}

console.log(latex`\unicode`)

// { cooked: undefined, raw: "\\unicode" }

注意:这一转义序列限制只对带标签的模板字面量移除,而不包括不带标签的模板字面量:

1
2
let bad = `bad escape sequence: \unicode`
// Uncaught SyntaxError: Invalid Unicode escape sequence

参考

http://exploringjs.com/es2018-es2019/toc.html

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/template_strings