ES9新特性解读

44 阅读8分钟

ES9(ECMAScript 2018)为JavaScript语言带来了多项新特性,这些特性旨在简化异步编程、对象操作等常见任务。主要包含:异步迭代器、 Promise.finally()、Rest/Spread 属性、正则表达式命名捕获组、正则表达式反向断言 正则表达式Unicode属性转义、正则表达式dotAll模式、模板字符串修订共八个新特性。

一、异步迭代器(Asynchronous Iterators)

特性说明

异步迭代器Async iterator 对象的 next() 方法返回一个 Promise,这个 Promise 的返回值可以被解析成 {value, done} 的格式。 异步迭代器允许我们使用for-await-of循环异步迭代数据,这对于处理异步数据源(如文件流、网络请求等)非常有用。

优势

  • 代码简洁:简化了异步操作的编写,使得异步代码更易于阅读和维护。
  • 流程控制:可以像同步迭代器一样控制异步操作的流程。

使用方法

async function* readLines(path) {
  let file = await fileOpen(path);
  try {
    while (!file.EOF) {
      yield await file.readLine();
    }
  } finally {
    await file.close();
  }
}
for await (const line of readLines(filePath)) {
  console.log(line);
}

注意事项

  • 兼容性:并非所有环境都支持异步迭代器,需检查目标环境是否支持。
  • 错误处理:异步操作可能会抛出异常,应当使用try...catch进行错误处理。

二、Promise.finally()

特性说明

Promise.finally()方法在Promise执行完毕后,无论结果是fulfilled还是rejected,都会执行的一个回调函数。

优势

  • 统一资源管理:无需关心Promise的状态,即可在操作完成后进行资源清理。
  • 代码整洁:避免在thencatch中重复相同的代码。

使用方法

fetch(url)
  .then(response => response.json())
  .catch(error => console.error('Error:', error))
  .finally(() => console.log('Fetch complete'));

注意事项

  • 执行时机finally回调不接收任何参数,无法得知Promise的状态。
  • 异步执行finally中的回调是异步执行的。

三、Rest/Spread 属性

特性说明

Rest属性允许我们将对象剩余的属性收集到一个新的对象中。Spread属性则允许我们将一个对象展开到另一个对象中。

优势

  • 灵活性:可以轻松创建对象的副本,或在创建新对象时包含现有对象的属性。
  • 简洁性:减少了重复编写代码的需要。

使用方法

// Rest属性
const { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
console.log(z); // { a: 3, b: 4 }
// Spread属性
const n = { ...z, x: 5 };
console.log(n); // { a: 3, b: 4, x: 5 }

ES5实现方法

// Rest属性
var obj = { x: 1, y: 2, a: 3, b: 4 };
var x = obj.x;
var y = obj.y;
var z = {};
for (var key in obj) {
  if (key !== 'x' && key !== 'y') {
    z[key] = obj[key];
  }
}
// Spread属性
var n = {};
for (var key in z) {
  n[key] = z[key];
}
n.x = 5;

注意事项

  • 浅拷贝:Spread操作是浅拷贝,不会复制对象的原引用类型的深层次结构。
  • 原型链:Spread操作不会包含对象的原型链上的属性。

四、正则表达式命名捕获组(Named Capture Groups)

特性说明

ES9允许在正则表达式中使用命名捕获组,这使得匹配的结果可以通过名称而不是数字索引来引用。

优势

  • 可读性:通过名称引用捕获组比数字索引更直观,特别是在复杂的正则表达式中。
  • 易于维护:当正则表达式结构发生变化时,只需要修改名称,而不必更新索引。

使用方法

const regex = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/;
const match = regex.exec('2023-04-01');
console.log(match.groups.year); // 2023
console.log(match.groups.month); // 04
console.log(match.groups.day); // 01

ES5实现方法

var regex = /(\d{4})-(\d{2})-(\d{2})/;
var match = regex.exec('2023-04-01');
var groups = {
  year: match[1],
  month: match[2],
  day: match[3]
};
console.log(groups.year); // 2023
console.log(groups.month); // 04
console.log(groups.day); // 01

注意事项

  • 兼容性:不是所有的JavaScript环境都支持命名捕获组,使用前需要检查。
  • 性能:在某些情况下,使用命名捕获组可能会稍微影响正则表达式的性能。

五、正则表达式反向(后顾)断言(Lookbehind Assertions)

特性说明

ES9引入了反向断言,允许正则表达式匹配字符串中的某个模式之前或之后的文本。/(?<=exp2)exp1/

前瞻断言 /exp1/(?=exp2)

优势

  • 灵活性:可以更精确地控制匹配的模式,特别是在需要依赖上下文的情况下。
  • 简洁性:避免了复杂的正则表达式结构。

使用方法

const regex = /(?<=\$)\d+/;
const match = regex.exec('$100');
console.log(match[0]); // 100

ES5实现方法

由于ES5不支持反向断言,通常需要通过字符串操作来模拟,这可能会非常复杂且效率低下。

注意事项

  • 贪婪顺序:贪婪匹配顺序从右到左 /(?<=(\d+)(\d+))$/.exec('1053') ['', '1', '053']
  • 反向引用顺序:断言内部反向引用使用时匹配组必须在右侧 /(?<=(.)\1)\d/.exec('a2bb3')/(?<=\1(.))\d/.exec('a2bb3') 两个重复的值后边的数字。
  • 性能:复杂的正则表达式可能会影响执行效率。

六、 RegExp Unicode Property Escapes(正则表达式Unicode属性转义)

说明

正则表达式Unicode属性转义是ES9引入的一个新特性,它允许在正则表达式中使用\p{...}\P{...}转义序列来匹配符合Unicode属性的字符。这为正则表达式提供了对Unicode字符集的更细粒度的控制。

优势

  • 强大的匹配能力:允许你精确匹配Unicode字符的属性,如字母、数字、标点符号等。
  • 代码简洁性:减少了使用复杂的字符类和Unicode范围来匹配特定属性字符的需要。

使用方法

const regex = /\p{Lowercase}/u; // 匹配任何小写字母
const regex2 = /\P{Lowercase}/u; // 匹配任何非小写字母
const match = regex.exec('a'); // 匹配成功,返回'a'
const match2 = regex2.exec('A'); // 匹配成功,返回'A'

在上述例子中,\p{Lowercase}匹配任何小写字母,而\P{Lowercase}匹配任何非小写字母。u标志是必须的,它指示正则表达式引擎处理Unicode属性转义。

ES5实现方法

在ES5中,没有直接的语法支持Unicode属性转义。你需要使用字符类和Unicode范围来模拟相同的功能。

const regex = /[a-z]/; // 匹配任何小写字母
const regex2 = /[^a-z]/; // 匹配任何非小写字母
const match = regex.exec('a'); // 匹配成功,返回'a'
const match2 = regex2.exec('A'); // 匹配成功,返回'A'

注意事项

  • 兼容性:Unicode属性转义是ES9引入的特性,不是所有JavaScript环境都支持。
  • 性能:使用Unicode属性转义可能会稍微影响正则表达式的性能。
  • 准确性:Unicode属性转义提供了一种精确匹配Unicode字符属性的方法,但需要注意某些属性可能会有多个字符匹配。

七、 正则表达式dotAll模式(s标志)

说明

在正则表达式中,点号(.)通常用于匹配除换行符外的任意单个字符。ES9引入了s标志(也称为dotAll模式),它允许点号匹配任意字符,包括换行符。这意味着在点号前加上s标志后,点号将不再跳过换行符。

优势

  • 匹配任意字符:使得点号在正则表达式中可以匹配任意字符,包括换行符。
  • 简化正则表达式:无需使用多个字符类来匹配任意字符,包括换行符。

使用方法

const regex = /foo.bar/s; // 匹配'foo\nbar'
const match = regex.exec('foo\nbar'); // 匹配成功,返回'foo\nbar'

在上述例子中,/foo.bar/s将匹配字符串'foo\nbar'中的'foo\nbar',包括换行符。

ES5实现方法

在ES5中,没有直接的语法支持s标志。你需要使用多个字符类或特定的Unicode范围来匹配任意字符,包括换行符。

const regex = /foo[\s\S]bar/; // 匹配'foo\nbar'
const match = regex.exec('foo\nbar'); // 匹配成功,返回'foo\nbar'

八、 Template Literal Revision(模板字符串修订)

说明

模板字符串修订是ES9引入的一个小特性,放松了对标签函数里面的字符串转义的限制。如果遇到不合法的字符串转义,就返回 undefined,而不是报错,并且从 raw 属性上面可以得到原始字符串。

之前的问题:

function latex(strings) {
  // ...
}

let document = latex`
\newcommand{\fun}{\textbf{Fun!}}  // 正常工作
\newcommand{\unicode}{\textbf{Unicode!}} // 报错
\newcommand{\xerxes}{\textbf{King!}} // 报错

Breve over the h goes \u{h}ere // 报错
`

上面代码中,变量 document 内嵌的模板字符串,对于 LaTEX 语言来说完全是合法的,但是 JavaScript 引擎会报错。原因就在于字符串的转义。

模板字符串会将\u00FF 和\u{42}当作 Unicode 字符进行转义,所以\unicode 解析时报错;而\x56 会被当作十六进制字符串转义,所以\xerxes 会报错。也就是说,\u 和\x 在 LaTEX 里面有特殊含义,但是 JavaScript 将它们转义了。

优势

  • 增强适用性:解决LaTEX里面特殊含义的字符和模板字符串转义字符的冲突.

使用方法


function tag(strs) {
  strs[0] === undefined
  strs.raw[0] === '\unicode and \u{55}'
}
tag`\unicode and \u{55}`

在这个例子中,tag函数的参数strings是一个包含原始模板字符串的数组,values是一个包含要插入到模板字符串中的值的数组。strings.raw[0]strings.raw[1]分别代表模板字符串的第一行和第二行。

注意事项

  • 兼容性:这种对字符串转义的放松,只在标签模板解析字符串时生效,不是标签模板的场合,依然会报错。。
let bad = `bad escape sequence: \unicode` // 报错