TC39 2024年会议提案,这些新语法已经可以试用了!

科技   2024-11-11 08:03   江苏  

当一个提案进入到 Stage 4 时,意味着提案已经可以在多个浏览器、Node.js 上试用,并且这些运行时都已经完成语言合规测试。同时,这个提案将会被吸纳到下一个年度发布的 ECMAScript 版本中,如 ECMAScript 2023 等。

ArrayBuffer transfer

提案地址:proposal-arraybuffer-transfer[1]

这个提案为 ArrayBuffer 原型增加了两个新的方法 ArrayBuffer.transferArrayBuffer.transferToFixedLength,填补了对 buffer 所有权进行转移的能力。

当我们需要读写 buffer 时,需要禁止外部对 buffer 的写入:

function validateAndWrite(arrayBuffer{
  // Do some asynchronous validation.
  await validate(arrayBuffer);

  // Assuming we've got here, it's valid; write it to disk.
  await fs.writeFile("data.bin", arrayBuffer);
}

const data = new Uint8Array([0x010x020x03]);
validateAndWrite(data.buffer);
setTimeout(() => {
  data[0] = data[1] = data[2] = 0x00;
}, 50);

根据validate(arrayBuffer)的执行时间,arrayBuffer 的内容可能由于先超时而执行传给定时器的回调而过期。一种防御性的写法在执行validate之前对 buffer 进行拷贝。

function validateAndWriteSafeButSlow(arrayBuffer{
  // Copy first!
  const copy = arrayBuffer.slice();

  await validate(copy);
  await fs.writeFile("data.bin", copy);
}

但这种传统的方式可能会造成性能低下,使用transfer方法进行所有权转移,可以通过零复制的方式高效完成:

function validateAndWriteSafeAndFast(arrayBuffer{
  // Transfer to take ownership, which implementations can choose to
  // implement as a zero-copy move.
  const owned = arrayBuffer.transfer();

  // arrayBuffer is detached after this point.
  assert(arrayBuffer.detached);

  await validate(owned);
  await fs.writeFile("data.bin", owned);
}

Sync Iterator Helpers

提案地址:proposal-iterator-helpers[2]

该提案为迭代器对象增加了一些新的便利的方法,例如在数组上常见的 map,filter,flatMap,reduce,forEach,some等方法,还增加了几个特有方法,用过 RxJS 的开发者应该会比较眼熟:

.take(limit)

限制生成元素的数量,并返回新的迭代器

functionnaturals({
  let i = 0;
  while (true) {
    yield i;
    i += 1;
  }
}

const result = naturals()
  .take(3);
result.next(); //  {value: 0, done: false};
result.next(); //  {value: 1, done: false};
result.next(); //  {value: 2, done: false};
result.next(); //  {value: undefined, done: true};

.drop(limit)

跳过前面指定数量的元素,并返回新的迭代器

functionnaturals({
  let i = 0;
  while (true) {
    yield i;
    i += 1;
  }
}

const result = naturals()
  .drop(3);
result.next(); //  {value: 3, done: false};
result.next(); //  {value: 4, done: false};
result.next(); //  {value: 5, done: false};

.flatMap(mapperFn)

扩展或扁平化嵌套结构,并返回新的迭代器

const sunny = ["It's Sunny in""""California"].values();

const result = sunny
  .flatMap(value => value.split(" ").values());
result.next(); //  {value: "It's", done: false};
result.next(); //  {value: "Sunny", done: false};
result.next(); //  {value: "in", done: false};
result.next(); //  {value: "", done: false};
result.next(); //  {value: "California", done: false};
result.next(); //  {value: undefined, done: true};

.toArray()

将迭代器生成的值转换为数组

functionnaturals({
  let i = 0;
  while (true) {
    yield i;
    i += 1;
  }
}

const result = naturals()
  .take(5)
  .toArray();

result // [0, 1, 2, 3, 4]

RegExp Modifiers

大多数正则表达式引擎,如 Perl、PCRE、.NET 和 Oniguruma,允许在子表达式中控制一些正则表达式标志(如忽略大小写、多行模式等),这在解析器、语法高亮和其他工具中非常有用。然而,ECMAScript 在这方面的支持不够完善。因此,该提案提出了正则表达式模式修饰符提案,旨在在正则表达式内部灵活控制这些标志。

该提案的主要目标是允许在子表达式中更改正则表达式标志,如:

  • i:忽略大小写
  • m:多行模式
  • s:单行模式(即 "dot all")
  • x:扩展模式(参阅 Regular Expression X Mode[3]

正则表达式修饰符语法如下:

  • (?imsx-imsx:subexpression):用于设置或取消设置(使用 -)子表达式的指定标志。
  • (?imsx-imsx):设置或取消设置(使用 -)从当前位置到下一个 ) 或模式结束的指定标志。
const re1 = /^[a-z](?-i:[a-z] "a-z")$/i;
re1.test("ab"); // true
re1.test("Ab"); // true
re1.test("aB"); // false

const re2 = /^(?i:[a-z])[a-z]$/;
re2.test("ab"); // true
re2.test("Ab"); // true
re2.test("aB"); // false

Import Attributes and JSON Modules

提案地址:proposal-import-attributes[4]

该提案为模块导入语句添加内联语法,以便在模块说明符之上传递更多信息。这一提案的初衷是在 JavaScript 环境中以通用方式支持新的模块类型。

语法如下,这里以导入 JSON 模块为例:

// static import
import json from "./foo.json" with { type"json" };
// dynamic import
import("foo.json", { with: { type"json" } });

该提案同时支持重导出的内联语法:

export { val } from './foo.js' with { type"javascript" };

在不同宿主环境(Node.js, web)通常提供不同的方式来加载模块。可以通过导入属性来实现这些方式的传递。

  • Worker 实例化:
new Worker("foo.wasm", { type"module"with: { type"webassembly" } });
  • HTML 集成:

虽然 TC39 不会对 HTML 进行规范更改,但期望的用法每个导入属性可以成为 HTML 属性,在 script 标签中使用。

<script src="foo.wasm" type="module" withtype="webassembly"></script>

Promise.try

提案地址:proposal-promise-try[5]

我们知道,Promise 能够帮助我们很好地简化错误处理,你可以放心地在调用链中进行逻辑处理,只需要确保放置了 .catch 函数,比如这样:

function processUser(id: number{
  return queryUser(id)
    .then((user) => queryAccount(user))
    .then((account) => queryGroup(account))
    .then((group) => queryAuth(group))
    .catch(() => {
      /* ... */
    });
}

在这连续的三个 .then 语句调用中,任何一个语句的错误都会被 .catch 语句捕获,无论是 query 过程中的异步错误,还是拼写问题导致的同步错误(比如 queryAccount(usr))。Promise 本质上是通过隐式的 try catch 语句来实现这个效果的,然而这一隐式捕获的范围并不包括最开始的 queryUser(id),如果在这个方法中抛出一个同步错误,后续的 Promise 错误处理是无法捕获它的,这就导致了我们得非常丑陋地在外面显式套一个 try catch 语句。

当然也有更好的方式,比如用 p-try[6] 来包裹一下 Promise 调用链的起始调用:

import pTry from 'p-try';

function processUser(id: number{
  return pTry(queryUser, id)
    .then((user) => queryAccount(user))
    .then((account) => queryAuth(account))
    .catch(() => {
      /* ... */
    });
}

实际上 pTry 内部的逻辑非常之简单,它只是将起始的函数调用包裹在一个 Promise 的 resolve 内,从而使得 Promise 的调用链“向前”延伸了一节,从而将起始调用的同步错误也纳入到捕获范围内。

除了上述这样起始调用确定是异步函数的情况以外,实际上有些时候你也会面临这样的场景:无论一个函数是同步 or 异步,你都想将它包裹在 Promise 调用链内并通过 .catch 进行处理,尤其是这个函数调用可能来自第三方。因此此提案则提出,为 ECMAScript 内置 Promise.try 方法来支持这些场景。

Duplicate Named Cap Groups

提案地址:proposal-duplicated-named-capturing-groups[7]

在正则表达式中,我们可以使用捕获组(Capturing Group)来对匹配模式中的某一部分做独立的匹配,如 es+ 会匹配 essss 与 esssss(+ 代表匹配一次或更多),而使用匹配组,我们可以将 es 作为一个匹配部分,如 (es)+ 会匹配 es 以及 eseses 等。

我们也可以对捕获组进行命名,如 ?这样的形式,常见的一个场景是结合 str.match 方法:

const dateRegexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})/;
const str = "2022-06-01";

const groups = str.match(dateRegexp).groups;

groups.year; // 2022
groups.month; // 06
groups.day; // 01

无法使用同名捕获组匹配一组联合模式,如日期格式还可能是06-01-2022,我们希望能这么使用联合模式:

const dateRegexp = /(?<year>[0-9]{4})-(?<month>[0-9]{2})-(?<day>[0-9]{2})|(?<day>[0-9]{2})-(?<month>[0-9]{2})-(?<year>[0-9]{4})/;

但由于捕获组的命名唯一约束,上面这个表达式是不合法的。

为了解决这一问题,此提案提出允许捕获组的命名不唯一,以此来支持如上面在联合模式中使用捕获组的场景。

Set Methods

提案地址:proposal-set-methods[8]

此提案为 JavaScript 中的Set结构新增了一批内置方法,主要为集合相关,包括交集、并集、差集、子集等:

  • Set.prototype.intersection(other)
  • Set.prototype.union(other)
  • Set.prototype.difference(other)
  • Set.prototype.symmetricDifference(other)
  • Set.prototype.isSubsetOf(other)
  • Set.prototype.isSupersetOf(other)
  • Set.prototype.isDisjointFrom(other)

这些方法的入参均为另一个Set类型的数据,或者至少是实现了 .size .keys .has 三个方法的对象。

参考资料
[1]

proposal-arraybuffer-transfer: https://github.com/tc39/proposal-arraybuffer-transfer

[2]

proposal-iterator-helpers: https://github.com/tc39/proposal-iterator-helpers

[3]

Regular Expression X Mode: https://github.com/rbuckton/proposal-regexp-x-mode

[4]

proposal-import-attributes: https://github.com/tc39/proposal-import-attributes

[5]

proposal-promise-try: https://github.com/tc39/proposal-promise-try

[6]

p-try: https://www.npmjs.com/package/p-try

[7]

proposal-duplicated-named-capturing-groups: https://github.com/tc39/proposal-duplicate-named-capturing-groups

[8]

proposal-set-methods: https://github.com/tc39/proposal-set-methods






  • 我是 ssh,工作 6 年+,阿里云、字节跳动 Web infra 一线拼杀出来的资深前端工程师 + 面试官,非常熟悉大厂的面试套路,Vue、React 以及前端工程化领域深入浅出的文章帮助无数人进入了大厂。
  • 欢迎长按图片加 ssh 为好友,我会第一时间和你分享前端行业趋势,学习途径等等。2024 陪你一起度过!


  • 关注公众号,发送消息:
    指南获取高级前端、算法学习路线,是我自己一路走来的实践。
    简历获取大厂简历编写指南,是我看了上百份简历后总结的心血。
    面经获取大厂面试题,集结社区优质面经,助你攀登高峰
因为微信公众号修改规则,如果不标星或点在看,你可能会收不到我公众号文章的推送,请大家将本公众号星标,看完文章后记得点下赞或者在看,谢谢各位!

前端从进阶到入院
我是 ssh,只想用最简单的方式把原理讲明白。wx:sshsunlight,分享前端的前沿趋势和一些有趣的事情。
 最新文章