前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ES2018新特性学习

ES2018新特性学习

作者头像
李振
发布2021-11-26 09:50:50
6410
发布2021-11-26 09:50:50
举报
文章被收录于专栏:乱码李

ECMAScript 2018 (ES9) 在 6 月底正式发布,带来了很多新特性。关于 ES7 和 ES8 相关的知识,可以查看这篇文章 ES2016 和 ES2017 学习。目前大部分 ES7 和 ES8 的特性都得到主流浏览器的支持,而 ES9 的新特性还未能实现很好的兼容性。

关于 ES7/8/9 全部特性可以查看 tc39 官方的 proposals,这些都是最后进入 stage 4 的特性。

ES9 的新特性:

  1. Lifting template literal restriction 模板语法修正
  2. s (dotAll) flag for regular expressions (正则表达式 dotAll 模式)
  3. RegExp named capture groups (正则表达式命名捕获组)
  4. Rest/Spread Properties (Rest/Spread 属性)
  5. RegExp Lookbehind Assertions (正则表达式反向(lookbehind)断言)
  6. RegExp Unicode Property Escapes (正则表达式 Unicode 转义)
  7. Promise.prototype.finally
  8. Asynchronous Iteration (异步迭代器)

正则表达式 dotAll 模式

dotAll 是一个新的正则表达式修饰符,目前 JS 拥有的修饰符有:

  • g -> global
  • i -> ingoreCase
  • m -> multiline
  • y -> sticky
  • u -> unicode
  • s -> dotAll

正则表达式中的 . 用来匹配任何单个字符,但是有 2 个除外:多字节 emoji 字符和行终结符。

代码语言:javascript
复制
let regex = /^.$/
regex.test('😀')   // false

通过设置 u 表示 unicode

代码语言:javascript
复制
let regex = /^.$/u
regex.test('😀')   // true

行终止符包括

  • U+000A LINE FEED (LF) (\n) - 换行
  • U+000D CARRIAGE RETURN (CR) (\r) - 回车
  • U+2028 LINE SEPARATOR - 行分隔符
  • U+2029 PARAGRAPH SEPARATOR - 段分隔符

还有一些其它字符,也可以作为一行的开始:

  • U+000B VERTICAL TAB (\v)
  • U+000C FORM FEED (\f)
  • U+0085 NEXT LINE

目前 . 只能匹配其中的一部分:

代码语言:javascript
复制
let regex = /./

regex.test('\n')       // false
regex.test('\r')       // false
regex.test('\u{2028}') // false
regex.test('\u{2029}') // false

regex.test('\v')       // true
regex.test('\f')       // true
regex.test('\u{0085}') // true

标记 s 表示 dotAll,用来改变 . 不能匹配行终止符的行为:

代码语言:javascript
复制
/hello.world/.test('hello\nworld')  // false
/hello.world/s.test('hello\nworld') // true

或者用 \s 来匹配空白符:

代码语言:javascript
复制
/hello.world/.test('hello\nworld')  // false
/hello[\s]world/s.test('hello\nworld') // true

dotAll 表示 . 可以匹配任意字符:

代码语言:javascript
复制
const re = /hello.world/s  // 等价于 const re = new RegExp('hello.world', 's')

re.test('hello\nworld') // true
re.dotAll // true
re.flags // 's'

正则表达式命名捕获组

捕获组就是把正则表达式中匹配到的内容,保存到内存中以数字编号或者显式命名的数组里,方便后面使用。这种引用既可以在正则表达式内部,也可以是在正则表达式外部。

捕获组有两种形式,一种是普通捕获组,另一种是命名捕获组。

代码语言:javascript
复制
const regex = /(\d{4})-(\d{2})-(\d{2})/
const matchers = regex.exec('2018-07-02')
matchers[0]    // 2018-07-02
matchers[1]    // 2018
matchers[2]    // 07
matchers[3]    // 02

使用数字捕获组的一个缺点是对于引用不太直观,以上面的例子,我们很难分清楚哪个组代表的是年,哪个组代表的是月。而命名捕获组就是为了解决这个问题。

命名捕获组

ES2018 允许命名捕获组可以使用 (?<name>...) 语法给每个组起一个名字。

代码语言:javascript
复制
const regex = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/
const match = regex.exec('2018-07-02')
console.log(match.groups.day) // '02'
console.log(match.groups.month) // '07'
console.log(match.groups.year) // '2018'

名字唯一

每个捕获组的名字必须唯一,否则会抛出异常。

代码语言:javascript
复制
const regex = /(?<foo>\d)-(?<foo>\d)/
// Uncaught SyntaxError: Invalid regular expression: /(?<foo>\d)-(?<foo>\d)/: Duplicate capture group name

匹配失败

任何匹配失败的命名组都将返回 undefined。

代码语言:javascript
复制
let re = /^(?<optional>\d+)?$/
const matchers = re.exec('')

matchers[0] === ''
matchers.groups.optional === undefined

使用解构赋值

代码语言:javascript
复制
let re = /^(?<one>.*):(?<two>.*)$/
let {groups: {one, two}} = re.exec('foo:bar')
console.log(`one: ${one}, two: ${two}`)  // 输出 one: foo, two: bar

使用 replace

代码语言:javascript
复制
const reDate = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/
const d = '2018-07-02'
console.log(d.replace(reDate, '$<month>-$<day>-$<year>')) // 07-02-2018

String.prototype.replace 第 2 个参数可以接受一个函数。这时 命名捕获组的引用会作为 groups 参数传递进去:

代码语言:javascript
复制
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/

let result = '2018-07-02'.replace(re, (...args) => {
  let {day, month, year} = args[args.length - 1]
  return `${day}-${month}-${year}`
})

result === '02/07/2018' // true

反向引用

当需要在正则表达式里面引用命名捕获组时,使用 \k<name> 语法。

代码语言:javascript
复制
let duplicate = /^(?<half>.*).\k<half>$/
duplicate.test('a*b') // false
duplicate.test('a*a') // true

向下兼容

/(?<name>)//\k<foo>/ 只有在命名捕获组中才有意义。如果正则表达式没有命名捕获组,那么 /\k<foo>/ 仅仅是字符串字面量 “k” 而已。

代码语言:javascript
复制
/\k<foo>/.test('k<foo>')   // true

正则表达式反向(lookbehind)断言

断言 (Assertion) 是一个对当前匹配位置之前或之后的字符的测试,它不会实际消耗任何字符,所以断言也被称为“非消耗性匹配”或“非获取匹配”。

正则表达式的断言一共有 4 种形式:

  • (?=pattern) 零宽正向肯定断言(zero-width positive lookahead assertion)
  • (?!pattern) 零宽正向否定断言(zero-width negative lookahead assertion)
  • (?<=pattern) 零宽反向肯定断言(zero-width positive lookbehind assertion)
  • (?<!pattern) 零宽反向否定断言(zero-width negative lookbehind assertion)

正向断言(lookahead)

当前位置后面的字符串应该满足断言,但是并不捕获,在当前的 JavaScript 正则表达式只支持正向断言。

代码语言:javascript
复制
const regex = /li(?=zhen)/
const match1 = regex.exec('lizhen')
console.log(match1[0]) // li
// 如果字符串没有zhen,则无法匹配
const match2 = regex.exec('liming')
console.log(match2) // null

正向否定断言正好相反

代码语言:javascript
复制
const regex = /li(?!zhen)/

const match1 = regex.exec('lizhen')
console.log(match1)    // null

const match2 = regex.exec('liming')
console.log(match2[0]) // li

反向断言(lookbehind)

反向断言和正向断言的行为一样,只是方向相反。反向肯定断言使用语法 (?<=...)

比如我们想获取所有的人民币金额,但是不获取其它货币(比如美元):

代码语言:javascript
复制
const regex = /(?<=\D)\d+(\.\d*)?/
const match = regex.exec('$123.89')
console.log(match[0]) // 123.89

正则表达式 Unicode 转义

Unicode 标准为每个符号分配各种属性和属性值,比如希腊字母 π 在 Unicode 中有独特的属性和属性值。目前版本的 ECMAScript 中正则表达式是无法匹配这些 Unicode 的,通常开发人员有两种选择。

(1) 在运行时使用类似于 xregexp 这样的库创建增强的正则表达式:

代码语言:javascript
复制
const regexGreekSymbol = XRegExp('\\p{Greek}', 'A')
regexGreekSymbol.test('π') // true

缺点是 xregexp 是一个运行时依赖,对性能要求较高的 web 应用来说不是很理想。而且其压缩文件 xregexp-all-min.js.gz 也有 35k,并且每当 Unicode 标准更新时,必须要更新 xregexp 才能使用新数据。

(2) 在编译时的时候使用 regenerate 生成正则表达式。

代码语言:javascript
复制
const regenerate = require('regenerate')
const codePoints = require('unicode-9.0.0/Script/Greek/code-points.js')
const set = regenerate(codePoints)
set.toString()
// → '[\u0370-\u0373\u0375-\u0377\u037A-\u037D\u037F\u0384\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03E1\u03F0-\u03FF\u1D26-\u1D2A\u1D5D-\u1D61\u1D66-\u1D6A\u1DBF\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FC4\u1FC6-\u1FD3\u1FD6-\u1FDB\u1FDD-\u1FEF\u1FF2-\u1FF4\u1FF6-\u1FFE\u2126\uAB65]|\uD800[\uDD40-\uDD8E\uDDA0]|\uD834[\uDE00-\uDE45]'
// Imagine there’s more code here to save this pattern to a file.

虽然这种方法所生成的正则表达式相当大,但是能够得到最佳的运行时性能。最大的缺点是它需要一个构建脚本,每当 Unicode 标准更新时,必须更新生成脚本。

解决方案

ES2018 中使用 \p{…}\P{…} 进行 Unicode 的属性转义,在正则表达式中使用 u 进行标记。在 \p{…} 内,可以以键值对的方式设置需要匹配的属性,而非具体内容。比如要匹配希腊字母 π

代码语言:javascript
复制
const reGreekSymbol = /\p{Script=Greek}/u
reGreekSymbol.test('π') // true

解决了以下几个问题:

  1. 不用为创建 Unicode-aware 正则表达式担心。
  2. 不需要运行时依赖。
  3. 正则表达式不需要使用 Unicode 区间来判断特点的内容。
  4. 不需要生成正则表达式脚本。
  5. Unicode 属性转义自动保持最新,每当 Unicode 标准更新时,ECMAScript 引擎更新其数据即可。

Rest/Spread 属性

ECMAScript 6 中增加了数组的 Rest 解构赋值和 Spread 语法,比如:

代码语言:javascript
复制
var a, b, rest
[a, b, ...rest] = [10, 20, 30, 40, 50]
console.log(a) // 10
console.log(b) // 20
console.log(rest) // [30, 40, 50]
代码语言:javascript
复制
function sum(x, y, z) {
  return x + y + z
}
const numbers = [1, 2, 3]
sum(...numbers) // 6
sum.apply(null, numbers) // 6

ES2018 中增加了对象的 Rest 属性和 Spread 语法。

Rest 属性

代码语言:javascript
复制
let {x, y, ...z} = {x:1, y:2, a:3, b:4}
x // 1
y // 2
z // {a:3, b: 4}

Spread 语法

代码语言:javascript
复制
let n = {x, y, ...z}
n // {x:1, y:2, a:3, b:4}

Promise.prototype.finally

Promise.prototype.finally 早就有很多实现,以至于我一直都认为它是原生对象的原型属性。常见的实现有:

  1. Bluebird#finally
  2. Q#finally
  3. when#finally
  4. jQuery jqXHR#always
代码语言:javascript
复制
Promise.resolve(2).finally(() => {}) // will be resolved with 2

Promise.reject(3).finally(() => {}) // will be rejected with 3

Asynchronous Iteration

关于 JavaScript 的异步循环,我在之前的文章JavaScript 循环与异步有过探索。如今 ECMAScript 中有了对异步迭代的原生支持。

迭代器 Iterator

ES6 中引入迭代器来遍历数组,JavaScript 中的迭代器是一个对象,提供 next() 方法,用来返回序列中的下一项,这个方法包含两个属性:done 和 value。

迭代器对象一旦被创建,就可以反复调用 next()。

代码语言:javascript
复制
function makeIterator (array) {
  var nextIndex = 0
  return {
    next: function () {
      return nextIndex < array.length ?
        {value: array[nextIndex++], done: false} :
        {done: true}
    }
  }
}
// 初始化后 next() 方法可以用来依次访问对象中的键值
var it = makeIterator(['a', 'b'])
console.log(it.next().value) // 'a'
console.log(it.next().value) // 'b'
console.log(it.next().done)  // true

可迭代对象

常见的可迭代对象有:Array、String、TypedArray、Map、Set。这些对象都内置可迭代的对象,在其原型中有一个 Symbol.iterator 方法。

我们也可以定义可迭代对象。

代码语言:javascript
复制
var myIterable = {}
myIterable[Symbol.iterator] = function* () {
  yield 1
  yield 2
  yield 3
}

for (let value of myIterable) { 
  console.log(value)
}
// 1
// 2
// 3

var [a, b, c] = [...myIterable] // [1, 2, 3]
// a ===1; b === 2; c === 3
Array.from(myIterable) // [1, 2, 3]

当我们定义了可迭代对象后,就可以在 Array.fromfor...of 中使用这个对象。

异步迭代器

一个异步迭代器就像一个迭代器,除了它的 next() 方法返回一个 { value, done } 的 promise。如上所述,我们必须返回迭代器结果的 promise,因为在迭代器方法返回时,迭代器的下一个值和 done 状态可能未知。

代码语言:javascript
复制
const myAsyncIterator = {
  [Symbol.asyncIterator]: () => {
  	const items = ['a', 'b', 'c', 'd']
  	return {
  	  next: () => Promise.resolve({
        done: items.length === 0,
        value: items.shift()
  	  })
  	}
  }
}

对于异步迭代器,使用 for await of 进行迭代。

代码语言:javascript
复制
(async function () {
  for await (const item of myAsyncIterator) {
    console.log(item)
  }
})()

异步生成器函数

异步生成器函数与生成器函数类似,但有以下区别:

  1. 当被调用时,异步生成器函数返回一个对象:async generator,该对象有 3 个方法(next,throw,和 return),每个方法都返回一个 Promise,Promise 返回 { value, done }。而普通生成器函数并不返回 Promise,而是直接返回 { value, done }。这会自动使返回的异步生成器对象具有异步迭代的功能。
  2. 允许使用 await 表达式和 for-await-of 语句。
  3. 修改了 yield* 的行为以支持异步迭代。
代码语言:javascript
复制
async function* myAsyncGenerator() {
  let t = 0
  const test = function() {
    return new Promise((resolve, reject) => {
      setTimeout(() => {
        resolve(+new Date())
      }, 1000)
    })
  }
  try {
    while (t++ < 10) {
      yield await test()
    }
  } finally {
    console.log('finally')
  }
}

async function main() {
  for await (const item of myAsyncGenerator()) {
    console.log(item)
  }
}
main()

函数返回一个异步生成器(async generator)对象,可以用在 for-await-of 语句中使用。

参考文档

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2018-07-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 正则表达式 dotAll 模式
  • 正则表达式命名捕获组
    • 命名捕获组
      • 名字唯一
        • 匹配失败
          • 使用解构赋值
            • 使用 replace
              • 反向引用
                • 向下兼容
                • 正则表达式反向(lookbehind)断言
                  • 正向断言(lookahead)
                    • 反向断言(lookbehind)
                    • 正则表达式 Unicode 转义
                      • 解决方案
                      • Rest/Spread 属性
                        • Rest 属性
                          • Spread 语法
                          • Promise.prototype.finally
                          • Asynchronous Iteration
                            • 迭代器 Iterator
                              • 可迭代对象
                                • 异步迭代器
                                  • 异步生成器函数
                                  • 参考文档
                                  领券
                                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档