ECMAScript 双月报告:TC39 7月会议提案进度汇总

简介: 阿里巴巴目前是 ECMA 的合作会员,阿里经济体前端委员会会有代表参加相关会议讨论,我们会持续为大家分享 TC39 核心会议上关于 ECMAScript 的最新进展,欢迎大家关注。

作者 | 昭朗

阿里巴巴目前是 ECMA 的合作会员,阿里经济体前端委员会会有代表参加相关会议讨论,我们会持续为大家分享 TC39 核心会议上关于 ECMAScript 的最新进展,欢迎大家关注。

Stage 3 -> Stage 4

从 Stage 3 进入到 Stage 4 有以下几个门槛:

  1. 必须编写与所有提案内容对应的 tc39/test262 测试,用于给各大 JavaScript 引擎和 transpiler 等实现检查与标准的兼容程度,并且 test262 已经合入了提案所需要的测试用例;
  2. 至少要有两个实现能够兼容上述 Test 262 测试,并发布到正式版本中;
  3. 发起了将提案内容合入正式标准文本 tc39/ecma262 的 Pull Request,并被 ECMAScript 编辑签署同意意见。

Promise.any & AggregateError

提案链接
目前 Promise 主要的组合操作会有:
image.png
一个简单的 Promise.any 的使用例子:

Promise.any([
  fetch('https://example.com/').then(() => 'home'),
  fetch('https://example.com/blog').then(() => 'blog'),
  fetch('https://example.com/docs').then(() => 'docs')
]).then((first) => {
// 任一 Promise 被 resolve.
console.log(first);
// → 'home'
}).catch((error) => {
// 当上述所有的 Promise 都被 reject.
console.log(error);
});

从例子中我们也可以看到,Promise.any 只有在所有的输入的 Promise 都被 reject 后才会被 reject,那么它的 catch 所获取到的参数会如何表示 reject Promise.any 的所有异常信息呢?

这即是这个提案另一个部分 AggregateError。AggregateError 由一系列 Error 组成,可以通过 AggregateError.errors 属性获取这些 Error 实例。

WeakRefs & FinalizationRegistry

提案链接
在 ES6 中引入的 WeakMap 和 WeakSet 也可以来实现的某种程度的弱引用,不过这个弱引用不是真的弱引用,本质上其实是 Ephemeron 表。在 Ephemeron 的实现中,key 是对象,只要 key 一直还在那么 value 就不会释放。而 WeakRef 则没有这个问题,通过 WeakRef 生成的引用,存储在普通的 Map 中时也可以正常释放。

在此之前,JavaScript 里引用释放没有回调触发。新的 WeakRef 提案里有类似析构函数的 FinalizationRegistry 支持。FinalizationRegistry 与析构机制的区别在于,析构函数能够拿到当前对象,而 FinalizationRegistry 中注册的回调并不能拿到被释放(或即将被释放)的对象,而是一个在往 FinalizationRegistry 中注册这个对象时,一同传入的 heldValue 参数。通过这个 heldValue 我们能够获知是哪一个对象被释放并触发了这一次的终结回调。

const cache = new Map();

const finalizationGroup = new FinalizationRegistry((name) => {
const ref = cache.get(name);
if (ref !== undefined && ref.deref() === undefined) {
    cache.delete(name);
  }
});

function getImageCached(name) {
const ref = cache.get(name); // 1
if (ref !== undefined) { // 2
const deref = ref.deref();
if (deref !== undefined) return deref;
  }
const image = performExpensiveOperation(name); // 3
const wr = new WeakRef(image); // 4
  cache.set(name, wr); // 5
  finalizationGroup.register(image, name); // 6
return image; // 7
}

Logical Assignment

提案链接
这个提案定义了逻辑操作并赋值表达式,包含新增的三个逻辑操作并赋值操作符,逻辑或并赋值 ||,逻辑与并复制 &&,空值赋值 ??=:

// "Or Or Equals"
a ||= b;
a || (a = b);

// "And And Equals"
a &&= b;
a && (a = b);

// "QQ Equals"
a ??= b;
a ?? (a = b);

function example(opts) {
  opts.foo = opts.foo ?? 'bar';
  opts.baz ??= 'qux';
}

NumericLiteralSeparator

提案链接
非常长的数字字面量都非常难以阅读,特别是有非常多重复的数字,甚至需要借助外部工具来辅助阅读📏。

1000000000   // 十万?百万?千万?
const FEE = 12300;
// 应该读成 12,300 吗? 或者因为它的单位是分(人民币),所以应该读成 123 元?

这个提案让我们可以使用 _ (U+005F) 作为长数字字面量的分隔符,让数字字面量更加易读。

10_0000_0000            // 所以这是十亿

let fee = 123_00;       // ¥123元 (12300 分)
let amount = 123_4500;  // 123万4500 (中文阅读习惯)
let amount = 1_234_500; // 1,234,500 (英文书写习惯)

// 同样可以用在小数部分与指数部分
0.000_001 // 百万分之一
1e10_000  // 10^(10 000)

Intl.ListFormat

提案链接
这是一个 ECMA 402 国际化提案。这个提案提供了基于语言的列表格式化。而列表格式化也是一个常见的本地化需求:

let lfmt = new Intl.ListFormat("zh", {
type: "conjunction", // "conjunction", "disjunction" or "unit"
style: "long", // "long", "short" or "narrow"
});

console.log(lfmt.format(["Anne", "John", "Mike"])); // "Anne、John和Mike"

Intl.DateTimeFormat dateStyle/timeStyle

提案链接
这个提案也是一个 ECMA 402 国际化提案。不同的语言、本地化偏好对于不同长度的日期时间格式化时,都有不同的偏好:

  • en-US 格式短日期 7/27/20,相当于选项 year 为 2-digit,month 为 short,day 为 numeric;
  • zh-CN 格式短日期 2020/7/27,相当于选项 year 为 numeric,month 为 numeric,day 为 numeric;

通过内置的标准化 dateStyle 和 timeStyle 选项,提供更加符合本地化配置的格式化,而不需要开发者自行根据本地化选项选择合适的格式化选项。

let dtf = new Intl.DateTimeFormat("zh", {
year: "numeric", // "numeric", "2-digit"
month: "numeric", // "short", "numeric", "2-digit"
day: "numeric", // "numeric", "2-digit"
});

console.log(dtf.format(new Date())); // "2020年7月27日"

// 通过新的 dateStyle/timeStyle 选项:
let dtf = new Intl.DateTimeFormat("zh", {
dateStyle: "short", // "full", "long", "medium" or "short"
});
console.log(dtf.format(new Date())); // "2020/7/27"

Stage 2 -> Stage 3

从 Stage 2 进入到 Stage 3 有以下几个门槛:1. 撰写了包含提案所有内容的标准文本,并有指定的 TC39 成员审阅并签署了同意意见;2. ECMAScript 编辑签署了同意意见。

iterator.items()

提案链接
很多时候,类似于 Python 中的数组负值索引可以非常实用。比如在 Python 中我们可以通过 arr[-1] 来访问数组中的最后一个元素,而不用通过目前 JavaScript 中的方式来访问 arr[arr.length-1]。这里的负数是作为从起始元素(即arr[0])开始的反向索引。

但是现在 JavaScript 中的问题是,[] 这个语法不仅仅只是在数组中使用(当然在 Python 中也不是),而在数组中也不仅仅只可以作为索引使用。像arr[1]一样通过索引引用一个值,事实上引用的是这个对象的 "1" 这个属性。所以 arr[-1] 已经完全可以在现在的 JavaScript 引擎中使用,只是它可能不是代表的我们想要表达的意思而已:它引用的是目标对象的 "-1"这个属性,而不是一个反向索引。

这个场景其实也不是第一次在TC39提出了,比如现在已经是 Stage 1 的 Array.prototype.lastItem 提案,而与 Array.prototype.lastItem 提案相比,这次这个提案提供了一个更加通用的方案,我们可以通过任意可索引的类型(Array,String,和 TypedArray)上的 .item 方法,来访问任意一个反向索引、或者是正向索引的元素。

Intl.Segmenter

提案链接
很多语言都有词分割与句分割。Unicode UAX 29 定义了文本元素的分割算法,可以在文本中找出不同文本元素的分界线(包括如中文,韩文,日文,泰文等基于词典分割的东亚语言)。这对于实现更加可靠的输入法、文本编辑器、文本处理都有非常大的帮助。

在 Unicode UAX 29 中定义的文本元素、词句分割算法的实现如果在浏览器、JavaScript 中原生实现的话,相比于开发者们引入自己的实现方案来说,可以节省非常多的带宽与内存。

let segmenter = new Intl.Segmenter("zh", {granularity: "word"});

// Use it to get an iterator for a string
let input = "我不是,我没有,你别瞎说。";
let segments = segmenter.segment(input);

// Use that for segmentation!
for (let {segment, index, isWordLike} of segments) {
console.log("segment at code units [%d, %d): «%s»%s",
    index, index + segment.length,
    segment,
    isWordLike ? " (word-like)" : ""
  );
}

// console.log output:
// segment at code units [0, 3): «我不是» (word-like)
// segment at code units [3, 4): «,»
// segment at code units [4, 5): «我» (word-like)
// segment at code units [5, 7): «没有» (word-like)
// segment at code units [7, 8): «,»
// segment at code units [8, 9): «你» (word-like)
// segment at code units [9, 10): «别» (word-like)
// segment at code units [10, 12): «瞎» (word-like)
// segment at code units [11, 12): «说» (word-like)
// segment at code units [12, 13): «。»

Stage 1 -> Stage 2

从 Stage 1 进入到 Stage 2 需要完成撰写包含提案所有内容的标准文本的初稿。

WeakRef CleanupSome

提案链接
WeakRef 与 FinalizationRegistry 允许 JavaScript 应用去观测垃圾回收的过程。这个观测性给实现带来了非常多潜在的问题,目前提案通过将可观测的时间点交由宿主环境如浏览器来决定,如什么时候 FinalizationRegistry 的回调会被调用等来规避这些问题。通常,宿主环境,如浏览器,都会通过 microtask queue 等基于循环队列的方式来实现这个方案:WeakRefs 从“可以观测到值”到无法观测到值“的变化会发生在每一个 microtask 检查点上(即所有的 Promise 任务执行完成);类似的,FinalizationRegistry 的 cleanup 回调也会在所有的 Promise 任务执行完成后被调用。所以,只有当我们把程序的执行权交还给事件队列后,WeakRef 与 FinalizationRegistry 才能按预期的行为工作。

而当场景结合上 Web Workers,SharedArrayBuffer,WebAssembly 等共享内存的场景,我们可能在一次执行中做非常多的计算工作、与其他执行环境通过共享内存与原子量交互的工作,这个过程中也没有将执行权交还给事件队列(或者很少将执行权交还给事件队列)。而 WebAssembly 的场景就非常匹配这个特殊情况。那么能不能有除了间断性地停止代码执行,交出执行权以外,其他的方法来让 WeakRef 与 FinalizationRegistry 工作呢?

这即是这个提案所期望解决的问题。FinalizationRegistry.prototype.cleanupSome接受一个函数作为参数(后续提案更新可能会直接使用 FinalizationRegistry 的 cleanup 回调),然后 JavaScript 实现可能会在后续调用这个回调,就如同 FinalizationRegistry 的 cleanup 回调一样,但是是以一个同步的方式完成。

值得注意的是,这个提案是从 WeakRefs 提案(Stage 3)中直接分离出来的提案,没有经过 Stage 1 的流程,而是直接作为 Stage 2 提案发起 Review。

Record and Tuple

提案链接
Record 与 Tuple 与通过 Object.free 冻结的对象与用户代码中的 class 对象的主要区别是 Record 与 Tuple 是原始类型。另外,Object 与 Array 的相等性取决于他们是否是同一个实例({} !== {}),而 Record 与 Tuple 是否相等取决于他们的值(#[0, 0] === #[0, 0])。除此之外,Record 与 Tuple 都可以使用与普通对象与数组一样的方式访问字段与元素,如((#[0, 0])[0] === 0)。

Record 与 Tuple 作为组合原始类型,同样可以作为原始类型在 JavaScript 中的各种场景使用:

const grid = new Map([
  [#[0, 0], "player"],
  [[0,0], "player"],
  [#{x:3, y:5}, "enemy"],
  [{x:3, y:5}, "enemy"],
]);
console.log(grid.get(#[0, 0])); // player
console.log(grid.get([0,0])); // undefined
console.log(grid.get(#{x:3, y:5})); // enemy
console.log(grid.get({x:3, y:5})); // undefined

JSON.parse source text access

提案链接

JSON.parse is lossy, even with a reviver function. Replacer function output is subject to re-serialization

JSON.parse 无法对超出 IEEE 754 精度的数字精准解析—即使通过现在的 reviver 函数参数也不行。类似的,在 JSON.stringify 中,我们也无法通过 replacer 函数参数序列化 JSON 中不存在的类型。

// Numbers are subject to IEEE 754 precision limits.
JSON.parse(" 9999999999999999")
// → 10000000000000000

// …and reviver functions receive already-parsed values.
JSON.parse(" 9999999999999999", (key, val) => BigInt(val))
// → 10000000000000000n

// Strings get quoted
JSON.stringify(9999999999999999n, (key, val) => String(val))
// → "\"9999999999999999\""

// Unserializeable types get rejected
JSON.stringify(9999999999999999n, (key, val) => val)
// → TypeError

这个提案提出给 reviver 函数与 replacer 函数分别增加一个新的参数,原始 source 文本与一个 rawTag Symbol。其中,对于 replacer 函数的修改是这次会议新提出的内容,目前还没有非常详细的使用场景例子与 spec 文本,需要后续继续跟踪对于 replacer 的修改如何解决序列化的问题。

// Numbers are still subject to IEEE 754 precision limits.
JSON.parse(" 9999999999999999")
// → 10000000000000000

// …but reviver functions gain access to the raw source.
JSON.parse(" 9999999999999999", (key, val, {source}) => BigInt(source))
// → 9999999999999999n

// Emit a literal sequence of BigInt digits
JSON.stringify(9999999999999999n, (key, val, {rawTag}) =>
  ({[rawTag]: String(val)}) )
// → "9999999999999999"

Stage 0 -> Stage 1

从 Stage 0 进入到 Stage 1 有以下门槛:1. 找到一个 TC39 成员作为 champion 负责这个提案的演进;2. 明确提案需要解决的问题与需求和大致的解决方案;3. 例子;4. 对 API 形式、关键算法、语义、实现风险等有讨论、分析。

Stage 1 的提案会有可预见的比较大的改动,以下列出的例子并不代表提案最终会是例子中的语法、语义。

await operations

提案链接
目前,await 关键字对于一个全是 Promise 数组来说无法达成类似 co 的语义:

// co
const co = require('co');
co(function* () {
var result = yield Promise.all([ Promise.resolve(true) ]);
console.log(result) // => [ true ];

var result = yield [ Promise.resolve(true) ];
console.log(result) // => [ true ];
});

// async/await
(async function () {
var result = await Promise.all([ Promise.resolve(true) ]);
console.log(result) // => [ true ];

var result = await [ Promise.resolve(true) ];
console.log(result) // => [ Promise {true} ];
})();

暂且不论 co 激进的语义能否让开发者更好地理解、产生语义二义性。但是至少如果 await *如果同时具有 Promise.all 的语义,那我们就无法给 await 增加其他常见的 Promise 组合操作语义了,如 race,allSettled,any 等。

所以目前提案提出了如 await.all 等语法糖,让常见的 Promise 组合操作更加便于书写、便于阅读:

// before
await Promise.all(users.map(async x => fetchProfile(x.id)))

// after
await.all users.map(async x => fetchProfile(x.id))

Array.prototype.unique()

提案链接

去重在数据处理中是一个非常非常常见的操作。我们可以通过 [...new Set(array)] 来比较方便地去重,但是这个方式只适用于原始量,如数字、字符串等,而无法对一系列复杂结构对象进行去重。

提案提出了一个可以接受一个函数作为获取去重标记的 Array.prototype.unique 方法,可以用于复杂结构对象的场景:

arr.unique()
// eq [...new Set(arr)] or …?

arr.unique(x => x.foo.bar.name)
// deduplicate by the provided function

ResizableArrayBuffer and GrowableSharedArrayBuffer

提案链接

这个提案主要是期望给 WebAssembly 的场景提供更加方面的内存扩展方式。目前调整一个 ArrayBuffer 的大小需要复制内容,但是复制非常慢,而且可能导致内存空间碎片化。

提案提出了两种新的 ArrayBuffer 类型:ResizableArrayBuffer 和 GrowableSharedArrayBuffer。

ResizableArrayBuffer 是一个内部存储区域可以拆卸的 ArrayBuffer。设计上希望 ResizableArrayBuffer 可以原地调整大小,但是提案没有对调整大小是否能够观测做要求(改变地址等)。同样,提案目前也没有对调整大小的方案有做描述。

let rab = new ResizableArrayBuffer(1024, 1024 ** 2);
assert(rab.byteLength === 1024);
assert(rab.maximumByteLength === 1024 ** 2);

rab.resize(rab.byteLength * 2);
assert(rab.byteLength === 1024 * 2);

GrowableSharedArrayBuffer 是可以在多个执行环境中共享的 ArrayBuffer,但是考虑到多个执行环境的同步,所以 GrowableSharedArrayBuffer 是一个只能增长而不能缩减大小的设计。

另外,提案目前对于可调整大小的 ResizableArrayBuffer 和 GrowableSharedArrayBuffer 的最大增长大小的边际条件既没有定义也没有最佳使用场景,会在接下来、和进入 Stage 2 后撰写 spec 文本的过程中,继续探索这些情况。


image.png
关注「Alibaba F2E」
把握阿里巴巴前端新动向

相关文章
|
IDE Java 开发工具
如何在Windows操作系统上安装PyCharm?
【7月更文挑战第5天】如何在Windows操作系统上安装PyCharm?
863 59
|
19天前
|
人工智能 缓存 前端开发
DeepSeek Harness 首发实测 + 入门教程,夯爆了!梁神我错了
DeepSeek Harness + DeepSeek V4 Pro 项目实战保姆级教程!手把手带你从零安装开源 AI 编程工具,开发架构图、知识讲解网站、3D 网页游戏、全栈 AI 应用 4 个项目,覆盖运行模式选择、插件安装与开发,看看能不能对标 Claude。
13089 82
DeepSeek Harness 首发实测 + 入门教程,夯爆了!梁神我错了
|
7天前
|
人工智能 自然语言处理 安全
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
本文聚焦阿里云2026年推出的三款自研AI办公产品,清晰拆解千问办公、Qoder Teams、Qoder CN的差异化定位与能力边界:千问办公主打职场全场景提效,支持自然语言指令一键完成PPT生成、数据分析等高频办公任务;Qoder Teams面向程序员团队,深度整合AI代码生成、团队协同与企业知识库能力;Qoder CN则专为金融、政务等强合规场景打造,实现数据不出境与VPC私有化部署。文章同步给出分场景选型指南与最新活动定价,帮助不同类型的企业按需组合产品,实现业务岗、研发岗与强合规场景的AI能力全覆盖。
阿里云千问办公、Qoder Teams、Qoder CN区别与选择指南:模型能力、适用场景与最新活动参考
|
2天前
|
缓存 人工智能 API
阿里云Qwen3.8‑Flash完整能力解析:模型特性、API调用实操与计费规则深度拆解
在AI应用快速落地的当下,开发者与企业选型大模型API,不再只单纯关注评测榜单分数,推理速度、上下文长度、多模态能力、工具调用稳定性以及实际调用成本,共同决定项目能否平稳上线。Qwen3.8‑Flash作为新一代多模态混合专家模型,主打高性能推理与低成本开销,面向编程开发、智能Agent工作流、超长文档解析、图文混合理解等高频场景,提供托管API服务,权重同时开放可供本地部署,兼容主流接口协议,能够无缝接入各类开发工具链。很多开发者在接入过程中,容易混淆普通按量Token计费、缓存计费、各类订阅计划之间的差异,造成实际账单超出预估。本文从模型底层架构、核心功能能力、适用场景、API调用实操、完
674 0
|
12天前
|
Web App开发 人工智能 API
16 个超火的 DeepSeek Harness 插件,大肥鱼已经落后 N 个版本了。。。
DeepSeek Harness 精选插件推荐合集,从图片识别、浏览器操控、多 Agent 协作到手机远程控制,一口气带你看完 DSH 社区热门的十几个插件,覆盖技能扩展、UI 界面增强、整活玩法三大类,让你的鲸鱼变得更强。
1724 4
|
13天前
|
人工智能 Java BI
【AI】DeepSeek Harness 安装、运行、管理插件
本文介绍了如何运行DeepSeek开源的Agent框架DeepSeek Harness(dsh)。主要内容包括:使用nvm安装适配的Node版本;通过代理加速克隆GitHub源码;使用pnpm安装依赖并启动项目;配置DeepSeek API Token;安装扩展功能的插件。该框架自带Web界面,支持模型适配、文件编辑等插件化功能
1899 1
|
人工智能 JavaScript 开发工具
DeepSeek Harness 本地安装与使用指南
DeepSeek Harness(DSH)是DeepSeek AI开源的Agent运行框架,支持本地文件操作、命令执行与工具调用。基于Cordis插件架构,具备高扩展性与强可控性,适合开发者搭建可控Agent环境或开展模型基准测试。当前为开发者预览版,需Node.js环境,推荐先用`npx @deepseek-ai/dsh web`快速体验。
5132 0
|
15天前
|
人工智能 JavaScript 测试技术
保姆级教程:DeepSeek Harness从安装到跑通测试,30分钟上手
DeepSeek Harness是DeepSeek开源的AI Agent运行时,主打“一行命令安装、5分钟跑通”。它让模型真正动手干活——读代码、跑测试、分析失败、生成修复方案。本文手把手教你30分钟从零上手,覆盖安装、配置、实测及避坑指南,助你快速掌握下一代AI编程范式。
|
7天前
|
人工智能 Linux iOS开发
Ollama使用教程:Ollama官网下载、Ollama本地部署大模型(2026最新)
Ollama 是一款免费开源的本地大模型运行工具,支持在 Windows/macOS/Linux 上离线运行 Qwen、DeepSeek、Llama 等主流开源模型,数据不出本机、隐私安全。提供 OpenAI 兼容 API,命令行一键拉取/运行/管理模型,无需联网,无调用限制,是开发者与 AI 爱好者部署本地 AI 助手的理想选择。(239 字)