作者 | 昭朗
TC39 1月會議在 1 月 29 日如期結束,以下是這次會議中成功争取到了階段性進展的提案的介紹、近期改動回顧等提案的進展總結。
Stage 3 -> Stage 4
從 Stage 3 進入到 Stage 4 有以下幾個門檻:
必須編寫與所有提案内容對應的 tc39/test262 測試,用于給各大 JavaScript 引擎和 transpiler 等實作檢查與标準的相容程度,并且 test262 已經合入了提案所需要的測試用例;
至少要有兩個實作能夠相容上述 Test 262 測試,并釋出到正式版本中;
發起了将提案内容合入正式标準文本 tc39/ecma262() 的 Pull Request,并被 ECMAScript 編輯簽署同意意見。
https://github.com/tc39/test262 https://github.com/tc39/ecma262
Intl.DateTimeFormat.prototype.formatRange
提案連結:
https://github.com/tc39/proposal-intl-DateTimeFormat-formatRange這個提案為 JavaScript 引入了在國際化場景格式化日期區間的能力。長期以來,不同文化背景下的日期書寫标記都有或多或少的細小差別,而為了給多樣化的使用者提供合适的日期文本說明,對于開發者來說是一個非常大的維護負擔。目前 ECMA402 作為 ECMA262 (ECMAScript)國際化專項,已經給 ECMAScript 帶來了許多非常實用的 Intl API。
這個提案引入的 API 可以通過
Intl.DateTimeFormat.prototype.formatRange
與
Intl.DateTimeFormat.prototype.formatRangeToParts
來使用。前者将日期區間格式化成文本,後者則将日期區間格式化成分割好的區間描述,開發者可以選擇性的使用其中的片段。
let date1 = new Date(Date.UTC(2007, 0, 10, 10, 0, 0));
let date2 = new Date(Date.UTC(2007, 0, 10, 11, 0, 0));
// > 'Wed, 10 Jan 2007 10:00:00 GMT'
// > 'Wed, 10 Jan 2007 11:00:00 GMT'
let fmt = new Intl.DateTimeFormat("en", {
hour: 'numeric',
minute: 'numeric'
});
fmt.formatRange(date1, date2);
// return value:
// '10:00 – 11:00 AM'
fmt.formatRangeToParts(date1, date2);
// return value:
// [
// { type: 'hour', value: '10', source: "startRange" },
// { type: 'literal', value: ':', source: "startRange" },
// { type: 'minute', value: '00', source: "startRange" },
// { type: 'literal', value: ' – ', source: "shared" },
// { type: 'hour', value: '11', source: "endRange" },
// { type: 'literal', value: ':', source: "endRange" },
// { type: 'minute', value: '00', source: "endRange" },
// { type: 'literal', value: ' ', source: "shared" },
// { type: 'dayPeriod', value: 'AM', source: "shared" }
// ]
目前提案所包含的内容已經可以在 Chrome 79 與 Node.js 12.9.0 中開始在生産環境中使用了。
Stage 2 -> Stage 3
提案從 Stage 2 進入到 Stage 3 有以下幾個門檻:
- 撰寫了包含提案所有内容的标準文本,并有指定的 TC39 成員審閱并簽署了同意意見;
- ECMAScript 編輯簽署了同意意見。
JSON Modules
https://github.com/tc39/proposal-json-modules這個提案原本是 Import Assertion 提案的一部分。不過在 2020 年 7 月的 TC39 會議中經過讨論決定将定義 JSON Modules 的部分從 Import Assertion 提案中分離出來,單獨讨論、演進。
這個提案為 ECMAScript Module 文法帶來了斷言子產品類型為 JSON 的能力。這樣引擎就可以以 JSON 模式解析目标子產品。對于開發者來說,明确地斷言子產品類型可以獲得更快的解析速度。
import json from "./foo.json" assert { type: "json" };
import("foo.json", { assert: { type: "json" } });
值得注意的是,因為采用的斷言的語義,如果目标子產品是一個 JavaScript 檔案或者其他不符合 JSON 文法定義的檔案,則會導緻子產品解析失敗,無法導入。同時,在浏覽器中,這也意味着目标子產品的類型(是否是 JSON,還是一個 JavaScript 子產品)不是取決于服務端傳回的 Content-Type HTTP Header 來判斷,而是需要開發者明确在代碼中指定類型,來解決提案在最初時所被挑戰的安全性問題。
Private Fields In Operator
https://github.com/tc39/proposal-private-fields-in-in這個提案提出了使用 in 操作符來判斷目前 Stage 3 Class Private Fields 提案引入的 #字段 是否在一個對象中存在。相比于直接通過通路私有字段
try { obj.#foo } catch { /* #foo not exist in obj */ }
來判斷一個對象是否有安裝對應的
#字段
來說,Private-In 可以區分是通路錯誤,還是真正沒有
#字段
,如以下場景通過 try-catch 就無法區分是否是通路異常還是
#字段
确實不存在:
class C {
get #getter() { throw new Error('gotcha'); }
static isC(obj) {
try {
obj.#getter;
return true;
} catch {
return false;
}
}
}
而通過 Private-In 可以簡單、直白地、與普通字段類似的 in 操作符語義來判斷一個 #field 是否存在在一個對象上:
class C {
#brand;
#method() {}
get #getter() {}
static isC(obj) {
return #brand in obj && #method in obj && #getter in obj;
}
}
值得注意的是,這個提案并沒有改變
#field
的語義。也就是說,在對象詞法作用域的外部還是無法通路這些 #字段 ,同樣也無法判斷這些
#字段
是否存在。另外,因為
in
通常用在 duck-type 的場景,而對于
#字段
來說,下面這個例子雖然看起來字段名是一樣的,但是對于每一個類來說,他們的
#字段
都是不同的(即使名字一樣),是以與普通字段的
in
還是有些許因
#字段
帶來的不同。
class C {
#foo;
staitc isC(obj) {
return #foo in obj;
}
}
class D {
#foo;
staitc isD(obj) {
return #foo in obj;
}
}
const d = new D();
C.isC(d);
// => false;
另一個值得注意的事情是,對于庫作者來說,使用者在某些場景下是可以在同一個 JavaScript 程式中使用多個版本的庫,比如 lodash 3.1.1 和 lodash 3.1.2,這取決于依賴管理的定義。而如果這些庫中使用了私有字段,則他們的對象是不具有互操作性的。這與上面這個同名字段例子的原因相同,這些不同版本的庫雖然庫名相同,但是實際上他們在程式中是具有獨立的運作上下文的,
class C
會被定義兩次,并且這兩次是毫無關系的,是以對于 3.1.1 版本的
C.isC
判斷會對 3.1.2 版本的執行個體
c
會判斷為
false
。這對于庫作者維護來說,是非常需要注意的一個事項。
最後需要注意的是,在下面這個場景中,Private-in 檢查可以造成正确但是不符合直覺的結果:
class Foo extends function(o) { return o; } {
#first = 1;
#second = (() => { throw null })();
static hasFirst = o => checkHas(() => o.#first);
static hasSecond = o => checkHas(() => o.#second);
}
let checkHas = fn => { try { return fn(), true; } catch { return false; } };
let obj = {};
try { new Foo(obj); } catch {}
console.log('obj.#first exists', Foo.hasFirst(obj)); // true
console.log('obj.#second exists', Foo.hasSecond(obj)); // false
這其中的重點是 JavaScript 的 class constructor 和多個
#字段
是可以部分初始化的(雖然
extends
一個函數的寫法也是讓人無法挪開視線,直拍大腿:你咋這麼能呢?但這不是問題重點 :D)。而如果使用 Private-in 來做 class brand check,即嚴格類型檢查,則有可能因為部分初始化而隻檢查了成功初始化的
#first
但是沒檢查
#seond
是否存在,導緻通路
#second
的時候可能抛出異常。特别是在實際場景中我們可能有非常多的
#字段
,為了解決這個問題,這次會議也提出了一個新的提案 Class Brand Checks,我們也會在後文詳細解析。
Class Staic Initializer Block
https://github.com/tc39/proposal-class-static-block自從有了 Class Private Fields,對于類的文法是不斷地有新的實踐與需求。這個提案提議的 Class Static 初始化塊會在類被執行、初始化時被執行。Java 等語言中也有類似的靜态初始化代碼塊的能力,Static Initialization Blocks。
提案中定義的初始化代碼塊可以獲得 class 内的作用域,如同 class 的方法一樣,也意味着可以通路類的
#字段
。通過這個定義,我們就可以實作 JavaScript 中的 Friend 類了。
let getX;
export class C {
#x
constructor(x) {
this.#x = { data: x };
}
static {
// getX has privileged access to #x
getX = (obj) => obj.#x;
}
}
export function readXData(obj) {
return getX(obj).data;
}
Stage 1 -> Stage 2
從 Stage 1 進入到 Stage 2 需要完成撰寫包含提案所有内容的标準文本的初稿。
JavaScript Module Blocks
https://github.com/tc39/proposal-js-module-blocks目前很多裝置都有非常多的處理器核心,JavaScript 代碼在多個執行單元(線程、程序,如 Web Worker,Node.js 程序,Node.js worker_threads)中同時執行也是越來越常見。但是現在的 JavaScript 對于多個執行單元間共享 JavaScript 代碼并沒有非常理想的方案,通常我們有以下幾個方案:
1、将不同執行單元間的代碼寫在不同的檔案裡:
const Worker = new Worker('./my-worker.js')
worker.postMessage({ action: 'add', params: [40, 2] })
worker.addEventListener('message', data => alert(`Result: ${data}`))
2、通過一個通用的 Worker 執行器,然後将期望執行的 JavaScript 直接以字元串形式發送過去并執行(即每一次執行 JavaScript 引擎都需要重新解析這段 JavaScript 代碼):
const result = await runInWorker('return "foobar"')
3、通過一個通用的 Worker 執行器,接受一個函數并将這個函數 toString 後直接以字元串發送執行,:
function add(lhs, rhs) {
// 這裡捕獲了外部變量的話,難以檢測
return lhs + rhs;
}
const result = await runInWorker(add, 40, 2)
// 不支援 async function
async function generateGraph() {
/** complex code */
}
這些方式要麼不夠工效,要麼效率較差、更有安全風險。而這個提案則提出了一個隔離了變量作用域的代碼塊,并且這個代碼塊中的代碼可以實作解析一次,到處使用,也意味着其中有任何文法問題,都可以快速被引擎第一次解析即可檢查出來:
let workerCode = module {
onmessage = function({ data }) {
let mod = await import(data);
postMessage(mod.fn());
};
};
let worker = new Worker(workerCode, { type: 'module' });
worker.onmessage = ({ data }) => alert(data);
worker.postMessage(module { export function fn() { return 'hello!' } });
ResizableArrayBuffer and GrowableSharedArrayBuffer
https://github.com/tc39/proposal-resizablearraybuffer這個提案可以給 Streaming、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 是一個隻能增長而不能縮減大小的設計。
Intl.LocaleInfo
https://github.com/tc39/proposal-intl-locale-info這個 ECMA402 國際化提案為 Intl 帶來了擷取使用者本地化偏好的資訊的 API。比如擷取使用者習慣的周開始日(常見的有周一和周日),周末定義,使用者的書寫方向等。
let zhHans = new Intl.Locale("zh-Hans")
zhHans.weekInfo
// {firstDay: 1, weekendStart: 6, weekendEnd: 7, minimalDays: 4}
zhHans.textInfo
// { direction: "ltr"}
在這個例子中,1 代表周一,7 代表周日,沿用了 ISO-8861 定義的方案,并且與 Temporal 提案 通用。
Intl.DisplayNames
https://github.com/tc39/intl-displaynames-v2很多 Intl API 都提供了各種值的國際化能力,如:
let dtf = new Intl.DateTimeFormat("en", {month: "long"})
dtf.format(new Date("2020-01-01")) // January
但是目前還沒有 Intl API 可以直接擷取“一月”這個名詞的國際化格式。
目前已有的
Intl.DisplayNames
API 隻包含基本的地名、語言、書寫系統、貨币的支援。而這個提案則對這個國際化資料進行了擴充,如星期(星期一等)、月份(一月等)、機關(米等)、時區(中原標準時間等)、月曆(公曆等)、計數系統(“第一”等)。
let dn = new Intl.DisplayNames("zh-Hans", {type: "month", style: "long"})
dn.of(1) // "1月"
// 科普特曆
let dn = new Intl.DisplayNames("en", {type: "month", style: "long",
calendar: "coptic"})
dn.of(1) // "Tout"
Stage 0 -> Stage 1
從 Stage 0 進入到 Stage 1 有以下門檻:
- 找到一個 TC39 成員作為 champion 負責這個提案的演進;
- 明确提案需要解決的問題與需求和大緻的解決方案;
- 有問題、解決方案的例子;
- 對 API 形式、關鍵算法、語義、實作風險等有讨論、分析。
Stage 1 的提案會有可預見的比較大的改動,以下列出的例子并不代表提案最終會是例子中的文法、語義。
Async do expressions
https://github.com/tc39/proposal-async-do-expressionsAsync do 表達式是 do 表達式提案 的 async 語義擴充。遺憾的是,do expression 提案在這次會議上因為以下幾個問題尋求進入 Stage 2 的共識失敗了:
let x = do {
if (random() < .5) {
0
} // else undefined
};
do exression 以 do body 中的最後一個語句值作為 do expression 的 completion value,在這種情況下,do expression 的最後一個語句不能是 for 語句等循環語句、try-catch 語句、變量聲明語句等等,因為這些語句作為 do expression 的尾語句的話,do expression 的 completion value 是容易造成歧義、或者引入了隐式
undefined
作為 completion value。但是提案又允許 if 語句作為尾語句,在這種情況下如果 if 沒有命中條件則 completion value 預設為
undefined
,而這樣的不一緻從文法上難以讓人信服。那麼什麼樣的文法是更加具有一緻性的呢?從設計理念來說,至少應該是 ban 掉所有可能造成隐式
undefined
completion value 的語句作為尾語句,或者讓這些語句能有一緻的文法設計避免隐式
undefined
作為 completion value。

另外幾個争議點則是 do expression 的 completion value 不需要通過 return 語句來顯式傳回。同時在 do expression 中不能使用 break、continue、return 等語句。這給 do expression 的文法上增加了更多的不一緻設計。
話題回到 async do expression。這個提案為 do 表達式引入了 async 修飾符,這樣我們可以在非 async 函數上下文中的 do 表達式中使用 await 關鍵字了,同時,async do 表達式的語義是 IIAFE(Immediately Invoked Async Function Expression,原地立刻調用的 async 函數)類似,其中的代碼是立刻執行的,直到第一個 await 為止。
Promise.all([
async do {
let result = await fetch('thing A');
await result.json();
},
async do {
let result = await fetch('thing B');
await result.json();
},
]).then(([a, b]) => console.log([a, b]));
Class Brand Checks
https://github.com/tc39/proposal-class-brand-check上文我們說到,Private-In 提案對于部分初始化的多
#字段
類的場景解決的問題非常有限,是以這次會議中,這個提案就是為了解決更直接的類類型檢查的場景:是不是這個類的真實執行個體?JavaScript 中,因為原型鍊是可以非常友善地修改的,我們也經常可以通過修改原型鍊來獲得一些便捷的能力。但是自從有了 Private Fields
#字段
,原型鍊上的
#方法
就無法在這些通過修改原型鍊的對象上使用了(這些對象上不存在應該存在的
#字段
),問題多多。為了讓開發者、庫作者便捷的探知這個問題,通過早期錯誤來避免難以排查的運作時問題,這個提案為 ECMAScript 帶來了檢查一個對象是一個類的真實執行個體的能力。
問題來了,那什麼是一個類的真實執行個體?在提案的定義中,一個對象如果被一個 class C 的 constructor 初始化過,那麼這個對象就是這個 class C 的真實執行個體。
class C {
static isC = o => class.hasInstance(o);
}
C.isC({}) // => false
C.isC(new Foo) // => true
class Foo extends function(o) { return o; } {
static isFoo = o => class.hasInstance(o);
}
const obj = {};
new Foo(obj);
Foo.isFoo(obj) // => true
上面這個案例中,前兩個例子非常好了解,普通對象不是 C 的真實執行個體,通過 new C 構造的對象是 C 的真實執行個體。但是第三個例子是什麼情況?這是因為一個派生類的 constructor 會調用 super(),而 super call 則會将其基類的 constuctor 傳回值綁定為目前的
this
,然後會執行該 constructor 的初始化流程,比如(如果有的話)将他的
#字段
安裝到這個
this
上。而我們這裡真實執行個體的定義是如果一個對象被 class constructor 初始化過,是以對于這個場景,這個對象确實被這個 constructor 初始化過,是以是這個類的真實執行個體。那麼問題來了,這個對象可能是基類的真實執行個體嗎?這些具體的語義與可能會碰到的問題,将會在這個提案的 Stage 1 之後,預計在 Stage 2 中定義。
RegExp set notation
https://github.com/tc39/proposal-regexp-set-notation許多正規表達式引擎都支援預設的字元集(通常都是 Unicode 的各種字元集),避免開發者需要在正規表達式中寫死字元集。同時提案也包含了字元集的交集、差集操作,便于自由組合多個字元集。
// 差集
[A--B]
// 交集
[A&&B]
// 嵌套字元集
[A--[0-9]]
比如下面這個正規表達式可以比對所有非 ASCII 數字,然後我們就可以将這些非 ASCII 數字轉換成 ASCII 數字:
[\p{Decimal_Number}--[0-9]]
或者比對所有非 ASCII 的 Emoji:
[\p{Emoji}--\p{ASCII}]
Revisiting RegExp escape
https://github.com/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 = new RegExp(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 \(\?\:\)"
Array find from last
https://github.com/tc39/proposal-array-find-from-last這個提案引入了
Array.prototype.findLast
Array.prototype.findLastIndex
。從
Array.prototype.lastIndexOf
和
Array.prototype.find
可以衍生得出這兩個新的 API 語義是與
Array.prototype.find
類似的,不過是從 Array 的尾部開始周遊尋找符合其第一個 callback 參數所期望的元素。
const array = [{ value: 1 }, { value: 2 }, { value: 3 }, { value: 4 }];
// find
array.findLast(n => n.value % 2 === 1); // => { value: 3 }
// findIndex
array.findLastIndex(n => n.value % 2 === 1); // => 2
array.findLastIndex(n => n.value === 42); // => -1
Defer module import eval
https://github.com/tc39/proposal-defer-import-eval子產品的懶加載一直是一個常見的啟動速度優化手段。但是目前的 ECMAScript Module import 語句都是立刻加載的,而
import()
調用則會将所有依賴這些懶加載子產品的調用都傳播成 async/await 模式的,給開發維護造成了一定負擔。
async function lazySomeMethod() {
const { someMethod } = await import("./my-module.js");
return someMethod();
}
// 我們必須将這個較少使用的同步函數改成 async 函數才能使用上懶加載的特性;
function actuallySynchronousButRarelyUsed() {
someMethod();
}
而提案就是為了期望解決這個問題,基于類似于 Import Assertion 提案 後續的改進部分 子產品屬性 的文法,對子產品的導入标記懶加載來避免 async/await 的傳播:
import {x} from "y" with { lazyInit: true }
import defaultName from "y" with { lazyInit: true }
import * as ns from "y" with { lazyInit: true }
當然這個方案給原生 async 的 JavaScript 執行環境帶來了更多的挑戰。并且在 Top Level Await 已經 Stage 3 的前提下,懶加載導入的語義怎麼将這些包含 TLA 的子產品給兼并成同步的語義呢?目前提案針對 TLA 還沒有一個讓人信服的方案。不過 Mozilla 的代表認為經過評估,JavaScript 子產品的加載時間不管是在 Web 還是其他平台都是受硬體(HDD、SSD 等)影響非常大,當然語義挑戰也非常大。目前提案期望解決的問題非常美好,期待會有更好的解決方案:)
Extend TimeZoneName Option
https://github.com/tc39/proposal-intl-extend-timezonename這個 ECMA402 國際化提案擴充了 Intl.DateTimeFormat 中的 timeZoneName 選項,支援更多的格式化選項。
let timeZoneNames = ["short", "long", "shortGMT", "longGMT", "shortWall", "longWall"];
timeZoneNames.forEach(function(timeZoneName) {
console.log((new Date()).toLocaleTimeString("zh-Hans", {timeZoneName}));
});
上午9:27:27 [PST]
上午9:27:27 [太平洋标準時間]
上午9:27:27 [GMT-8]
上午9:27:27 [GMT-08:00]
上午9:27:27 [PT]
上午9:27:27 [太平洋時間]
EraDisplay
https://github.com/tc39/proposal-intl-eradisplay這個 ECMA402 國際化提案擴充了 Intl.DateTimeFormat 的選項,增加了 eraDiaplay 選項,用來格式化紀元資訊。
提案提出的 eraDiaplay 支援三種選項:"never" 總是不格式化紀元;"always" 總是格式化紀元;"auto" 如果格式化年份并且紀元與目前時間曆法的紀元不同,則格式化紀元。
new Intl.DateTimeFormat("zh-Hans").format(new Date(-752,3,13))
// 之前 => "753/4/13"
// 提案 => "公元前 753/4/13 BC"
Intl LocaleMatcher
https://github.com/tc39/proposal-intl-localematcher通常支援國際化顯示的網站都會有較好地支援一組預設的本地化配置,而我們通常可以通過
Accept-Language
請求頭或者
navigator.language
來比對期望的本地化配置。但是使用者所期望的本地化配置不一定被我們所支援,這時候就需要給使用者比對最佳的本地化配置。
目前這個比對操作已經在 ECMA402 中定義,隻不過還沒有将這個操作通過 JavaScript API 暴露出來。這個提案就是将這個操作定義成了 JavaScript API。
Intl.LocaleMatcher.match(
/* requestedLocales */ ["zh-Hans", "en"],
/* availableLocales */ ["zh", "en"],
/* defaultLocale */ "en");
// => 'zh'
結語
日前由賀師俊 Hax 牽頭組建、阿裡巴巴前端标準化小組、中國 ECMAScript 社群活躍的同學等等一起管理的 JSCIG(JavaScript Chinese Interest Group),旨在代表中國國内的廣大 JavaScript 開發者和初學者的利益,在語言使用效能方面向 TC39 技術委員會提出建議。
目前 JSCIG 中的參與者都較為活躍,而且大多數都是國内 JS 社群的活躍布道者。不過目前隻有少數成員在 TC39 中。而且由于國内缺乏上遊浏覽器代表,是以還無法對很多議題産生比較強烈的影響,但是目前發展的趨勢還不錯。當然也跟目前 TC39 的議題大多數都是和工效議題相關。得益于 JSCIG 的推進,已經引起了 TC39 技術委員會的讨論,目前也正在組建 TC39 官方多語言社群(
https://deploy-preview-219--tc39-beta.netlify.app/zh-hans/));
JSCIG 在 GitHub 上開放讨論各種 ECMAScript 的問題,非常歡迎有興趣的同學可以參與讨論:
https://github.com/JSCIG/es-discuss/discussions。
關注「Alibaba F2E」
把握阿裡巴巴前端新動态