在本次会议上,Resizable ArrayBugger 提案成功进入到 Stage 4,很快就将在下一个 ES 版本中与我们见面。同时,本次会议上新增了 Joint Iteration、Stable Formatting 等五个提案首次进入到 Stage 1。

Stage 3 → Stage 4

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

Resizable and growable ArrayBuffers

提案链接:tc39/proposal-resizablearraybuffer
这个提案扩展了 ArrayBuffer 与 SharedArrayBuffer ,为它们的构造函数分别增加了一个参数,可以设置最大可以增长的大小maxByteLength,这给 Streaming、WebAssembly 等场景提供了一个更加方便、高效的内存扩展方式。目前调整一个 ArrayBuffer 的大小需要复制内容,但是因为复制非常慢,而且可能导致内存空间碎片化,实际实践中限制非常多。
置了maxByteLength的 ArrayBuffer 是一个内部存储区域可以拆卸的 ArrayBuffer。提案设计上希望这些 ArrayBuffer 可以原地调整大小,但是对于 JavaScript 代码来说,实际 JavaScript 引擎有没有对这个大小调整是否是原地的是无法观测到的(如在 JavaScript 中判断内部存储区域实际内存地址是否改变等)。
let rab = new ArrayBuffer(1024, { maxByteLength: 1024 ** 2 });assert(rab.byteLength === 1024);assert(rab.maxByteLength === 1024 ** 2);assert(rab.resizable);rab.resize(rab.byteLength * 2);assert(rab.byteLength === 1024 * 2);// Transfer the first 1024 bytes.let ab = rab.transfer(1024);// rab is now detachedassert(rab.byteLength === 0);assert(rab.maxByteLength === 0);// The contents are moved to ab.assert(!ab.resizable);assert(ab.byteLength === 1024);
SharedArrayBuffer 是可以在多个执行环境中共享的 ArrayBuffer,但是考虑到多个执行环境的同步问题,所以在提案中 SharedArrayBuffer 增加了grow(newByteLength)的方法,无法缩小 SharedArrayBuffer 的大小。

Stage 1 → Stage 2

当一个提案进入 Stage 2,意味着已经完成了特性细节的草稿设计,可能被用于实验性验证等早期实现。

RegExp escape

提案链接:tc39/proposal-regex-escaping
长期以来,如果我们希望将用户输入作为正则表达式来使用的话,都会因为安全问题而需要将用户输入的内容转义后再输入 RegExp 来使用。但是转义实现不一定能正确地将当前环境所支持的 RegExp 中,有特殊含义的字符完全枚举、并转义,或者是无法正确处理多 code unit 的 UTF8 字符(如'😂'.length === 2),这给开发者带来了较大的维护负担。
const str = prompt("Please enter a string");const escaped = RegExp.escape(str);const re = newRegExp(escaped, 'g'); // handles reg exp special tokens with the replacement.console.log(ourLongText.replace(re));
这个提案就是希望解决这个问题,提议新增一个RegExp.escape函数,可以将所有正则表达式中具有特殊含义的字符转义。
RegExp.escape("The Quick Brown Fox"); // "The Quick Brown Fox"RegExp.escape("Buy it. use it. break it. fix it.") // "Buy it\. use it\. break it\. fix it\."RegExp.escape("(*.*)"); // "\(\*\.\*\)"RegExp.escape("。^・ェ・^。") // "。\^・ェ・\^。"RegExp.escape("😊 *_* +_+ ... 👍"); // "😊 \*_\* \+_\+ \.\.\. 👍"RegExp.escape("\d \D (?:)"); // "\\d \\D \(\?\:\)"

Stage 0 → Stage 1

所有 ECMAScript 提案都需要论证所提特性的价值、解决方案可行性。当提案进入 Stage 1 意味着提案的价值与设计方案正式被 TC39 接收,并开始标准化流程。

Joint Iteration

提案链接:proposal-joint-iteration
如果你使用过 RxJs,那么对 zip 操作符应该不会陌生。zip 用于从多个 Observable 创建一个合并的 Observable,它产生的值会来自于组成的 Observable 产生值的组合,注意,zip 是会等待所有 Observable 都产生了第 n 个值后才产生自己的第 n 个值的:
import { of, zip, map } from'rxjs';const age$ = of(27, 25, 29);const name$ = of('Foo', 'Bar', 'Beer');const isDev$ = of(true, true, false);zip(age$, name$, isDev$).pipe( map(([age, name, isDev]) => ({ age, name, isDev }))).subscribe(x =>console.log(x));// Outputs// { age: 27, name: 'Foo', isDev: true }// { age: 25, name: 'Bar', isDev: true }// { age: 29, name: 'Beer', isDev: false }
在 JavaScript 中,你可以简单地将其理解为 Iterator,即我们需要合并多个 Iterator 并实现类似 zip 那样按照次序一一对应值的能力。有许多语言都内置了 zip 方法,如 Python、Rust 等。而在 JavaScript 中,目前还需要通过社区库如 iter-tools,lodash.zip 等实现,因此此提案提出为 JavaScript 原生支持 zip 方法及 zipLongest(若 Iterator 长度不一致时的处理)等。

Iterator Sequencing

提案链接:proposal-iterator-sequencing
有点类似于上一个提案,但 Iterator Sequencing 是基于多个 Iterator 创建出一个新的 Iterator,并按照顺序从组成的 Iterator 中生成值,大致效果与下面的代码一致:
let lows = Iterator.from([0, 1, 2, 3]);let highs = Iterator.from([6, 7, 8, 9]);let lowsAndHighs = function* () {yield* lows;yield* highs;}();Array.from(lowsAndHighs); // [0, 1, 2, 3, 6, 7, 8, 9]
这个写法略显繁琐,我们应当能够直接合并两个 Iterator,在 RxJs 中提供了 concat 操作符来实现这个效果:

类似的,Python 中提供了 concat 方法,Rust 中提供了 flatten 方法,以及 JavaScript 社区中的 extra-iterable 等。

Locale Extensions

提案链接:locale-extensions
想象这么一个场景,假设你是欧洲人,母语是英语,去了美国读大学。看起来可能避免了语言的问题,但其实日常生活还有不少差异,比如大学课程网站上显示的是 12小时制(而欧洲使用 24 小时制),每周的第一天是星期日而不是星期一,显示温度是华氏度而非摄氏度...。如果有这么一种方式,在保持当前语言(locale)不变的同时,添加额外的信息,告诉各个应用我希望如何展示这些信息,可能我的大学生活会更美好一点。
而此提案即是为了解决这个问题,它旨在允许 Web 页面从操作系统中访问到用户偏好信息的同时尽可能保障隐私性,比如上面这些偏好可以表示为'-u-fw-mon-hc-h23-mu-celsius'这么一个 locale extension 标志。常见的这些信息包括每周的第一天,12小时制/24小时制,华氏温度/摄氏温度以及阿拉伯数字/拉丁数字等等,如提案目前规划的 JavaScript API 是这样的:
navigator.localeExtensions['numberingSystem'];navigator.localeExtensions.numberingSystem;self.navigator.numberingSystem;//"deva"navigator.localeExtensions['hourCycle'];navigator.localeExtensions.hourCycle;self.navigator.hourCycle;//"h23"

Negated in

提案链接:proposal-negated-in-instanceof
JavaScript 中可以使用prop in obj以及ins instanctof Base这种方式来判断属性是否在对象或对象原型链上,以及 ins 是否是 Base 的实例。很常见的一种方式是我们可能需要对这个表达式的结果取反:
!(prop in obj);!(ins instanctof Base);
但某些时候,如果疏于检查,可能会写出这样的代码:
if(!prop in obj) { }if(!ins instanctof Base) { }
可别觉得这代码很难写出来,提案作者在 SourceGraph 上发现了数千个仓库有这样的问题,包括 MeteorBun 甚至是 WebKit
基于此原因,提案提出支持 Negated In/Instanceof Operator,如:
if(prop !in obj) { }if(ins !istanctof Base) { }
这种写法在其它语言中是相当常见的,如 Python:
if item notin items:passif ref1 isnot ref2:pass

Stable Formatting

提案链接:proposal-stable-formatting
目前,来自于 ECMA 402 规范的 Intl API 所提供的,对日期与时间的格式化结果依赖于浏览器实现,不存在标准格式,即各个浏览器之间会提供自己的实现,也就意味着对于格式化结果的解析代码在不同浏览器之间执行结果可能并不一致。
而此提案就希望解决这个问题,提供一个标准、稳定的格式化能力,它提出了两种可能的方式:
  1. 支持使用 null 作为 locale,如 (12345.67).toLocaleString(null) === '12345.67'(toLocaleString 方法是可以接受一个 locale 作为参数的),此时其等价于 ECMA 402 中的特殊 locale zxx,即表示使用明确定义过的标准行为,如在此 locale 下,使用 ISO 8601 作为日期格式的标准:new Intl.DateTimeFormat('zxx').format(new Date()) === '2023-10-11'
  2. 为 ECMAScript(ECMA 262)中的格式化 API 提供额外的参数,如 Date.prototype.toString()会变成 Date.prototype.toString([options]),注意, options 中的 locale 只会是 toLocaleString 方法入参 locales 的子集。 

总结

对 ECMAScript 有新想法、新需求?快来了解 TC39 是如何工作的(
https://yuque.antfin-inc.com/esnext/how-we-work/fgqi7o
),或者直接与 @昭朗 联系,加入 ESNext 研讨会小组(钉钉群号23188462)与我们讨论。
继续阅读
阅读原文