- 原文地址:Interesting ECMAScript 2017 proposals that weren’t adopted
- 原文做者:Kaelan Cooter
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:Colafornia
- 校對者:jasonxia23 kezhenxu94
要跟上全部新功能提案的進度並不容易。每一年,管理 JavaScript 發展的委員會 TC39 都會收到數十個提案。因爲大多數提案都不會到達第二階段,所以很難肯定哪些提案值得關注,哪些提案只是奇思妙想(或者稱之爲異想天開)。javascript
因爲如今愈來愈多的提案涌現出來,想要只停留在這些特性提案的頂層會很是困難。在過去介於 ES5 和 ES6 之間的六年時間裏 JavaScript 的發展腳步很是保守。自 ECMAScript 2016 (ES7) 發佈,發佈過程要求爲每一年發佈一次,而且更加標準化。html
隨着近些年來 polyfills 和轉譯器的流行,一些尚屬早期(early-stage)的提案甚至在還未最終肯定前就已經被普遍使用了。而且,因爲提案在被採納以前會有很大變更,一些開發者可能會發現他們所使用的特性永遠不會變成 JavaScript 的語言實現。前端
在深刻研究那些我以爲很好玩的提案以前,咱們先花點時間熟悉一下目前的提案流程。java
Stage 0 「稻草人」 —— 這是全部提案的起點。在進入下一階段以前,提案的內容可能會發生重大變化。目前尚未提案的接收標準,任何人均可覺得這一階段提交新的提案。無需任何代碼實現,規範也無需合乎標準。這個階段的目的是開始針對該功能特性的討論。目前已經有超過 20 個處於 stage 0 的提案。react
Stage 1 「提案」 —— 一個真正的正式提案。此階段的提案須要一個「擁護者」(即 TC39 委員會的成員)。此階段需仔細考慮 API 並描述出任何潛在的、代碼實現方面的挑戰。此階段也需開發 polyfill 併產出 demo。在這一階段以後提案可能會發生重大變化,所以需當心使用。目前仍處於這一階段的提案包括了已望穿秋水的 Observables type 與 Promise.try 功能。android
Stage 2 「草案」 —— 此階段將使用正式的 TC39 規範語言來精確描述語法。在此階段後仍由可能發生一些小修改,可是規範應該足夠完整,無需進行重大修訂。若是一個提案走到了這一步,那麼頗有可能委員會是但願最終能夠實現該功能的。ios
Stage 3 「候選」 —— 該提案已獲批准,僅當執行做者提出要求時纔會作進一步的修改。此時你能夠期待 JavaScript 引擎中開始實現提案的功能了。在這一階段草案的 polyfill 能夠安全無憂使用。git
Stage 4 「完成」 —— 說明提案已被採納,提案規範將與 JavaScript 規範合併。預計不會再發生變化。JavaScript 引擎將發佈它們的實現。截至 2017 年 10 月,已經有 9 個已完成的提案,其中最引人關注的是 async functions。github
因爲提案愈來愈多,思考一番,如下幾個提案是其中更有趣的。web
ECMAScript 2015 中引入了迭代器 iterator,其中包含了 for-of 循環語法。這使得循環遍歷可迭代對象變得至關容易,而且能夠實現你本身的可迭代數據結構。
遺憾的是,遍歷器沒法用於表示異步的數據結構如訪問文件系統。雖然你能夠運行 Promise.all 來遍歷一系列的 promise,但這須要同步肯定「已完成」的狀態。
例如,可使用異步迭代器來遍歷異步內容,按需讀取文件中內容,而不是提早讀取文件中的全部內容。
你能夠經過簡單地同時使用 generator 生成器語法和 async-await 語法來定義異步生成器函數:
async function* readLines(path) {
let file = await fileOpen(path);
try {
while (!file.EOF) {
yield await file.readLine();
}
} finally {
await file.close();
}
}
複製代碼
異步生成器函數示例。
能夠在 for-await-of 循環中使用這個異步生成器:
for await (const line of readLines(filePath)) {
console.log(line);
}
複製代碼
使用 for-await-of。
任意具備 Symbol.asyncIterator 屬性的對象都被定義爲 async iterable,而且可以使用於新的 for-await-of 語法中。這有一個具體可運行的示例:
class LineReader() {
constructor(filepath) {
this.filepath = filepath;
this.file = fileOpen(filepath);
}
[Symbol.asyncIterator]: {
next() {
return new Promise((resolve, reject) => {
if (this.file.EOF) {
resolve({ value: null, done: true });
} else {
this.file.readLine()
.then(value => resolve({ value, done: false }))
.catch(error => reject(error));
}
});
}
}
}
複製代碼
使用 Symbol.asyncIterator 的示例。
這一提案 目前處於 stage 3,瀏覽器已經開始實現了。處於這一階段意味着它頗有可能會被合併入標準並能夠在主流瀏覽器中使用。可是,在此以前,規範可能會有一些小修改,所以如今使用異步迭代器會帶來必定程度的風險。
regenerator 項目目前已爲異步迭代器提案提供了基本支持。可是,它自己並不支持 for-await-of 循環語法。Babel 插件 transform-async-generator-functions 既支持異步生成器又支持 for-await-of 循環語法。
這個提案 建議向 [ECMAScript 2015 中引進的 class 語法] (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) 中加入公共字段、私有字段與私有方法。該提案是通過多個競爭提案漫長討論和競爭後的結果。
使用私有字段和方法與對應的公共字段和方法相似,可是私有字段名方法名前會有一個 # 號。任何被標記爲私有的方法和字段都不會在類以外可見,從而確保內部類成員的強封裝。
下面是一個類 React 組件的假設示例,組件在私有方法中使用了公共和私有字段:
class Counter {
// 公共字段
text = ‘Counter’;
// 私有字段
#state = {
count: 0,
};
// 私有方法
#handleClick() {
this.#state.count++;
}
// 公共方法
render() {
return (
<button onClick={this.handleClick.bind(this)}>
{this.text}: {this.#state.count.toString()}
</button>
);
}
}
複製代碼
使用了私有字段與私有方法的類 React 組件。
Babel 目前尚未提供私有類字段與方法的 polyfill,可是不久就會實現。公共字段已有 Babel 的transform-class-properties 插件支持,但它依賴於一個已被合併入統一公共/私有字段提案的老提案。此提案於 2017 年 9 月 1 日 進入 stage 3,所以使用任何可用的 polyfill 都是安全的。
提案在被引入後也可能發生翻天覆地的變化,裝飾器就是一個很好的例子。Babel 的第五代版本實現了本來 stage 2 階段裝飾器的規範,其將裝飾器定義爲接收 target,name 與屬性描述的函數。如今最流行的轉譯裝飾器方式是經過 Babel 的transform-legacy-decorators 插件,其實現的是舊版的規範。
新的提案 大不相同。再也不做爲具備三個屬性的函數,如今咱們對改變描述符的類成員 —— 裝飾器進行了正式描述。新的「成員描述符」與ES5中引入的屬性描述符接口很是類似。
如今有兩種具備不一樣 API 的不一樣類型的裝飾器:成員裝飾器與類裝飾器。
在規範中,「額外」是指可由裝飾器添加的成員描述符的可選數組。這將容許裝飾器動態建立新的屬性與方法。好比,你可讓裝飾器給屬性建立 getter 與 setter 函數。
與舊規範相似,新規範容許修改類成員的描述符。此外,仍然容許在對象字面量的屬性上使用裝飾器。
在最終肯定以前,規範極可能會發生重大變化。語法中有一些模棱兩可之處,舊規範的許多痛點尚未獲得解決。裝飾器是語言的一個大型語法擴展,所以能夠預料到這種延遲。遺憾的是,若是新的提案被採納,你將不得不徹底重構你的裝飾器函數,以適用於新的接口。
許多庫做者選擇繼續支持舊的提案和 Babel 的 legacy-decorators 插件,即便新的提案已經處於 stage 2,舊的仍然處於 stage 0。core-decorators 做爲最受歡迎的使用裝飾器的 JavaScript 開源庫,就採用了這種方法。將來幾年中,庫的做者們頗有可能會繼續支持舊的提案。
也有可能這一新提案會被撤回,取而代之的是另外一新提案,裝飾器提案有可能不會在 2018 年併入 JavaScript。你能夠在 Babel 完成新的轉譯插件 後使用新的裝飾器提案。
ECMAScript 第六版中添加了 import 語句,並最終肯定了新的模塊系統的語義。就在近期,主流瀏覽器發佈更新以提供對其的支持,儘管它們對於規範的實現略有不一樣。NodeJS 在 8.5.0 版本中對於仍帶有實驗標誌的 ECMAScript 的模塊規範提供了初步支持。
可是,該提案缺乏一種異步導入模塊的方法,這使得難以在運行時動態導入模塊。如今,在瀏覽器中動態加載模塊的惟一方法,就是動態插入類型爲 「module」 的 script 標籤,將 import 聲明做爲其文本內容。
實現異步導入模塊的一種內置方法是提案 動態 import 語法,它會調用一個「類函數」的導入模塊加載表單。這種動態導入語法能夠在模塊代碼與普通腳本代碼中使用,從而爲模塊代碼提供了一個方便的切入點。
去年有一個提案提出 System.import() 函數來解決這個問題,可是該提案沒有被採納進入最終的規範。新提案目前處於 stage 3,有望在年末前被列入規範中。
提議的可觀察類型 Observable type 提供了一種處理異步數據流的標準化方法。它們已經以某種形式在許多流行的 JavaScript 框架如 RxJS 中實現。目前的提案很大程度上借鑑了這些框架的思路。
Observable 對象由 Observable 構造器建立,接收訂閱函數做爲參數:
function listen(element, eventName) {
return new Observable(observer => {
// 建立一個事件處理函數,能夠將數據輸出
let handler = event => observer.next(event);
// 綁定事件處理函數
element.addEventListener(eventName, handler, true);
// 返回一個函數,調用它即去掉訂閱
return () => {
// 解除元素的事件監聽
element.removeEventListener(eventName, handler, true);
};
});
}
複製代碼
Observable 構造器的使用。
使用訂閱函數去訂閱一個 observable 對象:
const subscription = listen(inputElement, 「keydown」).subscribe({
next(val) { console.log("Got key: " + val) },
error(err) { console.log("Got an error: " + err) },
complete() { console.log("Stream complete!") },
});
複製代碼
使用 observable 對象。
subscribe() 函數返回了一個訂閱對象。這個對象具備取消訂閱的方法。
Observable 不該混淆於 已廢棄的 Object.observe 函數,Object.observe 是能夠觀察對象變化的一種方法。其已被 ECMAScript 2015 中更通用的的實現 Proxy 所替代。
Observable 目前處於 stage 1,但它已被 TC39 委員會標記爲 「ready to advance」 並得到了瀏覽器廠商的大力支持,所以有望很快推動到下一階段。如今你就已經能夠開始使用這一提案的特性了,有三種 polyfill 實現 可供選擇。
CoffeeScript 曾因以一切皆爲表達式 而名聲大噪,儘管它的流行程度已經衰減,可是它對 JavaScript 近期的發展產生了重大影響。
do 表達式提出了一種將多個語句包裝在一個表達式中的新語法。能夠以以下方式編寫代碼:
let activeToDos = do {
let result;
try {
result = fetch('/todos');
} catch (error) {
result = []
}
result.filter(item => item.active);
}
複製代碼
do 表達式示例。
do 表達式的最後一個語句將做爲完成值,被隱式地返回。
do 表達式在 JSX 中很是有用。與複雜的三元表達式不一樣,do 表達式可使得 JSX 中的流程控制更可讀。
const FooComponent = ({ kind }) => (
<div> {do { if (kind === 'bar') { <BarComponent /> } else if (kind === 'baz') { <BazComponent /> } else { <OtherComponent /> } }} </div>
)
複製代碼
JSX 的將來?
Babel 已有插件 可轉譯 do 表達式。此提案目前處於 stage 1,關於如何與 generator 和 async 函數一塊兒使用,還存在一些重要的開放問題,所以規範可能會發生重大變化。
受 CoffeeScript 啓發而來的又一個提案是 optional chaining,它帶來了一種訪問對象屬性的簡單方法,面對值有可能爲 undefined 和 null 的對象屬性無需使用冗長的三元運算符了。它與 CoffeeScript 的存在操做符相似,可是缺乏一些值得注意的特性,好比範圍檢查和可選賦值。
示例:
a?.b // 若是 `a` 是 null/undefined 則返回 undefined,不然則返回 `a.b` 的值
a == null ? undefined : a.b // 使用三元表達式
複製代碼
提案目前處於 stage 1,已有名爲 babel-plugin-transform-optional-chaining 的 Babel 插件實現。TC39 委員會在2017 年 10 月的最後一次會議 中對它的語法表示擔心,可是其仍有可能被採納。
編寫能在每一個環境中運行的 JavaScript 代碼並不容易。在瀏覽器中,全局對象是 window —— 除非處於 web worker 中,此時全局對象是它自己。在 NodeJS 中則爲 global,可是這是在 V8 引擎之上添加的東西,並非規範的一部分,因此直接在 V8 引擎中運行代碼時,global 對象不可用。
待標準化提案 提出了能夠在全部引擎和運行環境中使用的全局對象。提案目前處於 stage 3,所以不久便會被採納。
TC39 委員會正在審議五十多項活躍提案,其中還未包括二十多個處於 stage 0 還沒有推動的提案。
你能夠在 TC39 的 GitHub 頁面 查看活躍提案列表。能夠在 stage 0 提案模塊找到一些更粗略的想法,包括了像方法參數裝飾器和新的模式匹配語法。
也能夠在會議記錄 和議程 倉庫瞭解委員會的優先事項和目前正在處理的問題。演講資料也陳列在會議記錄中,若是你對演講有興趣,也能夠進行查閱。
在最近的幾回 ECMAScript 修訂中有幾個重要的語法修改提案,這彷佛也是一種趨勢。ECMAScript 正在加快變革腳步,每一年都有愈來愈多的提案,2018 版本彷佛會比 2017 版本採納更多的提案。
今年,在語言中添加改善「生活質量」的提案規模較小,如內置對象的類型檢查和給 Math 模塊添加度數與弧度助手提案。這類提案將添加到標準庫中,而非修改語法。它們容易進行 polyfill,有助於減小第三方庫的使用。因爲無需改變語法,因此很快就可使用,在提案階段花費的時間也較少。
新的語法當然優秀,但我更但願將來能夠見到更多這種類型的提案。JavaScript 常常被人詬病缺乏優秀的標準庫,但很明顯你們正在努力去改變。
LogRocket 是一個前端日誌記錄工具,它可讓你像在本身的瀏覽器中同樣重播問題。無需再猜想錯誤發生的緣由或是向用戶索要截圖和日誌轉存,LogRocket 容許重播會話來快速定位錯誤源頭。不管使用什麼框架,LogRocket 能夠在任意應用中使用,並擁有從 Redux,Vuex 和 @ngrx/store 中記錄上下文的插件。
除了能夠將 Redux 的 action 和 state 記入日誌,LogRocket 還能夠記錄控制檯日誌,JavaScript 報錯,調用棧信息,網絡請求、響應頭和實體信息,瀏覽器的元信息和自定義日誌。它還利用 DOM 在記錄頁面上的 HTML 和 CSS,即便是最複雜的單頁面應用程序,也能夠從新繪製像素級完美的視頻。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。