ECMAScript 双月报告:TC39 2021年12月会议提案进度汇总

简介: 作者:@穹心(weixuan.linweixuan)审校:@昭朗(chengzhong.wcz)本次 TC39 会议中,有部分提案在此前会议的讨论基础上进行了更新,如 Intl.Segmenter 提案在 10 月会议中已进入到 Stage 4,在此次会议中更新为 Intl.Segmenter V2 提案,并进入到 Stage 1。另外,较受关注的 proposal-record-tuple、pr

作者:@穹心(weixuan.linweixuan)

审校:@昭朗(chengzhong.wcz)

本次 TC39 会议中,有部分提案在此前会议的讨论基础上进行了更新,如 Intl.Segmenter 提案在 10 月会议中已进入到 Stage 4,在此次会议中更新为 Intl.Segmenter V2 提案,并进入到 Stage 1。另外,较受关注的 proposal-record-tupleproposal-destructuring-private与 proposal-decorators等提案也在本次会议中进行了更新,但没有推进到下一阶段。

Stage 3 → Stage 4

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

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

Extend timeZoneName

提案链接:proposa-intl-extend-timezonename

这一提案为 Intl.DateTimeFormat方法扩展了 timeZoneName 选项的可用值,来使得对日期和时间的格式化行为更加精确。

timeZoneName 选项主要控制在返回结果中时区名称的展示方式:

// 不指定时,默认不会显示时区名称:"2020/12/20"
new Intl.DateTimeFormat('zh-cn').format(date);

// 使用精简的时区名称:"2020/12/20 GMT+8"
new Intl.DateTimeFormat('zh-cn', { timeZoneName: "short" }).format(date);

// 使用本地化的时区名称:"2020/12/20 中国标准时间"
new Intl.DateTimeFormat('zh-cn', { timeZoneName: "long" }).format(date);

此提案在原可用值 short 与 long 的基础上引入了新的四个值:

  • shortOffset,如  GMT-8,即  short 下的 PST(Pacific Standard Time),简短地本地化 GMT 格式。
  • longOffset,如  GMT-08:00,精确地本地化 GMT 格式。
  • shortGeneric,如  PT(Pacific Time 的缩写),简短的通用时区名称。
  • longGeneric,如  太平洋时间,即  long 选项下的太平洋标准时间,精确的通用时区名称。

Intl.DisplayNames V2

提案链接:proposa-intl-displaynames-v2

此提案的上一个版本参见:proposal-intl-displaynames

Intl.DisplayNames API的主要目的是通过浏览器内置的数据,来减少在进行本地化工作时,开发者需要在代码中显式携带的 i18n 相关数据。此提案已经支持了语言、地区、货币等部分的本地化能力,V2 版本的主要变更是为此提案新增了包括历法(calendar)、时间单位(dateTimeField)等在内的更多能力。

首先看一下上一版本中支持的对于地区的本地化能力:

var regionNames = new Intl.DisplayNames(['zh-CN'], {type: 'region'});
console.log(regionNames.of('419')); // "拉丁美洲"
console.log(regionNames.of('BZ')); // "贝里斯"
console.log(regionNames.of('US')); // "美国"
console.log(regionNames.of('BA')); // "波斯尼亚与黑塞哥维那"
console.log(regionNames.of('MM')); // "缅甸"

V2 版本中引入的对于时间单位的本地化:

var dateTimeFieldNames = new Intl.DisplayNames("zh", { type: "dateTimeField" })

dateTimeFieldNames.of("era") // "纪元"
dateTimeFieldNames.of("year") // "年"
dateTimeFieldNames.of("month") // "月"
dateTimeFieldNames.of("quarter") // "季度"
dateTimeFieldNames.of("weekOfYear") // "周"
dateTimeFieldNames.of("weekday") // "工作日"
dateTimeFieldNames.of("dayPeriod") // "上午/下午"
dateTimeFieldNames.of("day") // "日"
dateTimeFieldNames.of("hour") // "小时"
dateTimeFieldNames.of("minute") // "分钟"
dateTimeFieldNames.of("second") // "秒"

Stage 2 → Stage 3

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

  1. 撰写了包含提案所有内容的标准文本,并有指定的 TC39 成员审阅并签署了同意意见;
  2. ECMAScript 编辑签署了同意意见。

Array Grouping

提案链接:proposal-array-grouping

在对数组进行操作时,我们经常会遇到需要将数组中的元素根据某些特征来进行分类的场景。这个提案引入了一个新的数组方法 Array.prototype.groupBy ,来实内置的数组分组能力(类似于 Lodash 中的 groupBy方法):

const array = [1, 2, 3, 4, 5];

// =>  { odd: [1, 3, 5], even: [2, 4] }
array.groupBy((num, index, array) => {
  return num % 2 === 0 ? 'even': 'odd';
});

另外,这一提案还引入了 Array.prototype.groupByToMap 方法,此方法接收的处理函数返回可以一个对象,方法的返回值为 Map 结构:

const odd  = { odd: true };
const even = { even: true };

// =>  Map { {odd: true}: [1, 3, 5], {even: true}: [2, 4] }
array.groupByToMap((num, index, array) => {
  return num % 2 === 0 ? even: odd;
});

Stage 1 → Stage 2

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

Array.fromAsync

提案链接:proposal-array-from-async

Array.from 方法用于从类数组(Array-Like)或可迭代对象(Iterable)创建一个新的数组实例。ECMAScript 2018 引入了异步可迭代对象(Async Iterable)以及 for await of 语法以后,由于缺少直接从异步可迭代对象生成数组的内置方法,社区出现了 it-all这一类工具库,它的源码也非常简单:

const all = async (source) => {
  const arr = []

  for await (const entry of source) {
    arr.push(entry)
  }
  return arr
}

而 Array.fromAsync 提案则带来了语言层面的内置支持,如一个异步 Generator 函数使用 Array.fromAsync 的例子:

async function * asyncGen (n) {
  for (let i = 0; i < n; i++)
    yield i * 2;
}
// [0, 2, 4, 6].
const arr = [];
for await (const v of asyncGen(4)) {
  arr.push(v);
}
// 两种方式的结果是一致的
const arr = await Array.fromAsync(asyncGen(4));

注意,fromAsync 方法返回的是一个 Promise。

除了接受一个类数组或异步可迭代对象以外,类似于 from 方法,fromAsync 方法还能接受两个可选的参数。

  • mapFn,类似于 forEach 与 map,此回调函数会被应用在每一个数组成员 yield 后返回的值,这一点类似于  Array.from 接受的 mapFn 参数,但不同之处在于 fromAsync 中这一回调函数可以是异步的。
  • thisArg,调用 fromAsync 时的 this 对象。

对于错误处理,由于 fromAsync 方法返回一个 Promise,在创建过程中如果出现错误(包括读取(异步)可迭代对象、回调函数执行中出错等),则此 Promise 状态将被置为 rejected。

CoreJS 中已经提供了相应的 polyfill,见 array-from-async

RegExp Modifiers

提案链接:proposal-regexp-modifiers

目前,RegExp 支持多种执行模式,包含 i(大小写通配),m(多行匹配),s(单行匹配),还有目前同样作为 TC39 提案的 x(增强模式,见RegExp X Mode),等等。但是这些模式只能对整个正则表达式启用,而无法控制只对于其中的某一个部分生效,这带来了一定的限制。

RegExp Modifiers 提案在两月前的 10 月会议上由 stage 0 推进到 stage 1,为正则表达式引入了两种新的子表达式:

  • 自约束(self-bounded) (?imsx-imsx:subexpression) :在子表达式作用域内启用或禁用 flag。如 (?-i:A(?i:B)C) 匹配  ABC 与  AbC,但是不能匹配  aBC  或  ABc
  • 无约束(unbounded) (?imsx-imsx):从当前位置开始到表达式结尾启用 flag,并在遇到下一个相同修饰符时 flag 被覆盖。如 (?-i)A(?i)B(?-i)C将匹配  ABC 与  AbC(?-i) 代表在作用域内取消大小写通配, (?i) 代表在作用域内启用大小写通配),相反的, (?-i) 则代表取消。

在本次的 12 月 TC39 会议上,unbounded 模式被移除,并且不会再作为此提案的一部分,以下是 self-bounded 的使用示例:

// 为 [a-z] 表达式取消大小写通配模式
const re1 = /^[a-z](?-i:[a-z])$/i;
re1.test("ab"); // true
re1.test("Ab"); // true
re1.test("aB"); // false

RegExp Buffer Boundaries

提案链接:proposal-regexp-buffer-boundaries

目前在正则表达式中, ^ 与 $ 对于内容开头与结尾的匹配会受到 m(多行匹配模式)flag 的影响:

const pattern = String.raw`^foo$`;
const re1 = new RegExp(pattern, "u");
re1.test("foo"); // true
re1.test("foo\nbar"); // false

const re2 = new RegExp(pattern, "um");
re1.test("foo"); // true
re1.test("foo\nbar"); // true

RegExp Buffer Boundaries 提案在两月前的 10 月会议上由 stage 0 推进到 stage 1,引入了三种缓冲边界(Buffer Boundaries)标志,用于在正则表达式中匹配原始输入的开头或结尾。这一行为类似 ^ 或者 $ (但缓冲边界不会被 m flag 影响 )。

  • \A:匹配输入的开头; 
  • \z:匹配输入的结尾; 
  • \Z:零宽断言,由缓冲边界的换行符(可选)组成,等价于  (?=\R?\z)。 

在本次的 12 月 TC39 会议上,零宽断言 \Z 被暂时移除,但仍然被保留为提案的一部分,只是没有进入 Stage 2。

其使用示例如下:

const pattern = String.raw`\Afoo\z`;
const re1 = new RegExp(pattern, "u");
re1.test("foo"); // true
re1.test("foo\nbar"); // false

// 启用多行匹配
const re2 = new RegExp(pattern, "um");
re1.test("foo"); // true
re1.test("foo\nbar"); // false

Stage 0 → Stage 1

从 Stage 0 进入到 Stage 1 有以下门槛:

  1. 找到一个 TC39 成员作为 champion 负责这个提案的演进;
  2. 明确提案需要解决的问题与需求和大致的解决方案;
  3. 有问题、解决方案的例子;
  4. 对 API 形式、关键算法、语义、实现风险等有讨论、分析。 Stage 1 的提案会有可预见的比较大的改动,以下列出的例子并不代表提案最终会是例子中的语法、语义。

Intl.Segmenter V2

提案链接:proposal-intl-segmenter-v2

上一版本的提案链接:tc39/proposal-intl-segmenter

很多自然语言脚本(中文,英文,法文等等)都有词分割与句分割。Unicode UAX 29定义了文本元素的分割算法,可以在文本段落中找出不同文本元素的分界线(甚至包括如中文,韩文,日文,泰文等基于词典分割的东亚语言),这对于实现更加可靠的输入法、文本编辑器等一系列涉及到文本处理的工作都有相当大的裨益。

将 Unicode UAX 29中定义的文本元素、词句分割算法在浏览器、JavaScript 中原生实现后,相比于开发者们引入自己的实现方案来说,可以节省非常多的带宽与内存(不用再额外下载 CJK 词典了)。

上一版本的 Segmenter 提案已经支持了基于字母(grapheme)、词(word)、段落(sentence)的分割能力,但没有引入基于行(line)的分割能力。主要考量是行分割能力通常使用在文字的布局编排上,这涉及到更多的 API 以及相关工作量,甚至涉及到字符的实际渲染宽度。

上一版本中已支持的基于词的分割:

// 创建一个专用于特定语言的分词器,如这里是中文
let segmenter = new Intl.Segmenter("zh-CN", {granularity: "word"});

// 使用此分词器处理输入
let input = "我不是,我没有,你别瞎说。";
let segments = segmenter.segment(input);

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)" : ""
  );
}

其切分结果包含三个属性:

  • segment:此拆分单元的长度; 
  • index:此拆分单元的起始位置; 
  • isWordLike:当创建分词器时,如果设置粒度(granularity)精确为 "word" 级别,且此拆分单元近似于当前语言的单个单词("word-like"),则返回  true; 
  • input:原始输入值; 

结果如下:

// 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): «。»

而 V2 版本中引入的基于行的分割:

var s = new Intl.Segmenter("zh-cn", { granularity: "line" });
var ss = s.segment(
  "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动"
);

for (s of ss) {
  console.log(JSON.stringify(s));
}
// { "segment": "飞", "index": 0, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "虎", "index": 1, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "队", "index": 2, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "正", "index": 3, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "式", "index": 4, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "名", "index": 5, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "称", "index": 6, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "为", "index": 7, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "「中", "index": 8, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "华", "index": 10, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "民", "index": 11, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "国", "index": 12, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "空", "index": 13, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "军", "index": 14, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "美", "index": 15, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "籍", "index": 16, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "志", "index": 17, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "愿", "index": 18, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "大", "index": 19, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "队」,\n", "index": 20, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": true }
// { "segment": "1940 ", "index": 24, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "年", "index": 29, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "代", "index": 30, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "早", "index": 31, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "期", "index": 32, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "在", "index": 33, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "缅", "index": 34, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "甸", "index": 35, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "展", "index": 36, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "开", "index": 37, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "行", "index": 38, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false }
// { "segment": "动", "index": 39, "input": "飞虎队正式名称为「中华民国空军美籍志愿大队」,\n1940 年代早期在缅甸展开行动", "isHardBreak": false

返回结果中除 segment index input 以外,还新增了 isHardBreak 属性来表示当前的分割是否是强制的(如使用了 \n 换行符)。

总结

我们非常期待有提案使用场景或者新提案想法的同学与 @昭朗 联系,或者加入 ESNext 研讨会小组(钉钉群号23188462)与我们讨论。

相关文章
|
存储 机器学习/深度学习 监控
ECMAScript 双月报告:TC39 2023年3月会议提案进度汇总
在本次会议中,共有 9 个提案实现了 Stage 推进,其中阿里巴巴主导的 AsyncContext 提案进入到了 Stage 2。另外,有 4 个提案成功进入到 Stage 1,包括 Promise.withResolvers 以及 Class Method Param Decorators 等此前就广受关注的内置方法和语法提案。Stage 2 → Stage 3当一个提案进入 Stage 3 
|
JavaScript 算法 前端开发
ECMAScript 双月报告:TC39 2022年12月会议提案进度汇总
在本次会议中,Intl.Enumeration 提案成功进入到 Stage 4,距离它在 2020 年 6 月的会议上进入到 Stage 1 已经过去了两年半的时间,其它备受关注的提案如 Explicit Resource Management 与 Set Methods也成功取得进展,进入到 Stage 3 阶段。Stage 3 → Stage 4从 Stage 3 进入到 Stage 4 有以
|
存储 Web App开发 JSON
ECMAScript 双月报告:findLast 提案成功进入到 Stage 4
本次会议中,findLast 提案成功进入到了 Stage 4,这是第二个由中国开发者推动进入到 Stage 4 的提案。另外,较受关注的 String Dedent 与 JSON.parse source text access 等提案也在本次会议中取得了阶段性进展。
320 0
ECMAScript 双月报告:findLast 提案成功进入到 Stage 4
|
存储 JavaScript 前端开发
ECMAScript 双月报告:装饰器提案进入 Stage 3
ECMAScript 双月报告:装饰器提案进入 Stage 3
1063 0
|
Rust JavaScript 前端开发
ECMAScript 双月报告:TC39 2022年1月会议提案进度汇总
本次 TC39 会议中仅有 reversible-string-split 提案获得了阶段性进展,由 Stage 0 进入到 Stage 1,其他较受关注的提案如 array-from-async、原生枚举类型,以及在上一次会议中进入到 Stage 1 的 Intl.Segmenter v2 ,在本次会议中都没有取得阶段性进展。
290 0
|
Web App开发 存储 JavaScript
ECMAScript 双月报告:TC39 2021年4月会议提案进度汇总
来自2021年4月TC39会议关于 ECMAScript 的最新进展。
ECMAScript 双月报告:TC39 2021年4月会议提案进度汇总
|
JavaScript 前端开发 API
ECMAScript 双月报告:Realms 提案进入 Stage 3(2021/07)
7月的 TC39 会议在上周结束了。这次的会议有如 private-in 等提案进入了 Stage 4,Realms、`Object.hasOwn` 等提案进入了 Stage 3,相信很快大家就可以在开发者版本的浏览器、最新版 Node.js 中见到这些 API 了。那么这些提案提供了什么样的能力,我们该如何使用?
ECMAScript 双月报告:Realms 提案进入 Stage 3(2021/07)
|
Web App开发 存储 JavaScript
ECMAScript 双月报告:Realms 提案大改仍没能进入 Stage3
今年因为疫情原因,TC39 的会议频率大为提升,而每一次的会议内容也分散了许多。但是关于提案的讨论热度并不会因为疫情降低,比如这次会议中备受关注的 Realms 提案经过了大改:Realms 在新的提案方案里 Realm 之间无法直接交换除了原始 JavaScript 值(number, string, bigint, symbol 等)和 Callable 以外的 JavaScript 值。
ECMAScript 双月报告:Realms 提案大改仍没能进入 Stage3
|
Web App开发 JSON 人工智能
ECMAScript 双月报告:Error Cause 提案将在 ECMAScript 2022 中正式发布
这次 TC39 会议中,阿里巴巴提出的 Error Cause 提案成功进入 Stage 4 ,也是我国首个进入 Stage 4 的 TC39 提案。这个提案将在 ECMAScript 2022 中正式发布。
443 0
|
Web App开发 JavaScript 前端开发
ECMAScript 双月报告:阿里巴巴提案 Error Cause 进入 Stage3
从TC39 3月会议带来的ECMAScript最新进展。
ECMAScript 双月报告:阿里巴巴提案 Error Cause 进入 Stage3