前幾天 Google IO 上 V8 團隊爲咱們分享了《What's New in JavaScript》主題,分享的語速很慢推薦你們能夠都去聽聽就當鍛鍊下聽力了。看完以後我整理了一個文字版幫助你們快速瞭解分享內容,嘉賓主要是分享瞭如下幾點:javascript
開場就用 11x faster
數字把你們驚到了,也有不少同窗好奇究竟是怎麼作到的。其實這個優化並非最近作的,去年11月的時候 V8 團隊就發了一篇文章 《Faster async functions and promises》,這裏面就很是詳盡的講述瞭如何讓 async/await 優化到這個速度的,其主要歸功於如下三點:html
2008年 Chrome 出世,10年 Chrome 引入了 Crankshaft 編譯器,多年後的今天這員老將已經沒法知足現有的優化需求,畢竟當時的做者也不曾料想到前端的世界會發展的這麼快。關於爲什麼使用 TurboFan 替換掉 Crankshaft,你們能夠看看《Launching Ignition and TurboFan》,原文中是這麼說的:前端
Crankshaft 僅支持優化 JavaScript 的一部分特性。它並無經過結構化的異常處理來設計代碼,即代碼塊並無經過try、catch、finally等關鍵字劃分。另外因爲爲每個新的特性Cranksshaft都將要作九套不一樣的框架代碼適應不一樣的平臺,所以在適配新的Javascript語言特性也很困難。還有Crankshaft框架代碼的設計也限制優化機器碼的擴展。儘管V8引擎團隊爲每一套芯片架構維護超過一萬行代碼,Crankshaft也不過爲Javascript擠出一點點性能。
via: 《Javascript是如何工做的:V8引擎的內核Ignition和TurboFan》
而 TurboFan 則提供了更好的架構,可以在不修改架構的狀況下添加新的優化特性,這爲面向將來優化 JavaScript 語言特性提供了很好的架構支持,能讓團隊花費更少的時間在作處理不一樣平臺的特性和編碼上。從原文的數據對比中就能夠看到,僅僅是換了個編譯器優化就在 8 倍左右了…… 給 V8 的大佬們跪下了。java
而 Orinoco 新的 GC 引擎則是使用單獨線程異步去處理,讓其不影響 JS 主線程的執行。至於最後說的 async/await 的 BUG 則是讓 V8 團隊引起思考,將 async/await 本來基於 3 個 Promise 的實現減小成 2 個,最終減小成 1 個!最後達到了寫 async/await 比直接寫 Promise 還要快的結果。node
咱們知道 await
後面跟的是 Promise 對象,可是即便不是 Promise JS 也會幫咱們將其包裝成 Promise。而在 V8 引擎中,爲了實現這個包裝,至少須要一個 Promise,兩個微任務過程。這個在自己已是 Promise 的狀況下就有點虧大發了。而爲了實現 async/await 在 await 結束以後要從新原函數的上下文並提供 await
以後的結果,規範定義此時還須要一個 Promise,這個在 V8 團隊看來是沒有必要的,因此他們建議規範去除這個特性。git
最後的最後,官方還建議咱們:多使用 async/await 而不是手寫 Promise 代碼,多使用 JavaScript 引擎提供的 Promise 而不是本身去實現。github
隨着 Babel 的出現,JS 的語法糖簡直不要太多,Numeric Seperator 算是一個。簡單的說來它爲咱們手寫數字的時候提供給了分隔符的支持,讓咱們在寫大數字的時候具備可讀性。segmentfault
實際上是個很簡單的語法糖,爲何我會單獨列出來講呢,主要是由於它正好解決了我以前一個實現的痛點。我有一個需求是一堆文章數據,我要按照產品給的規則去插入廣告。如圖非紅框是文章,紅框處是廣告。因爲插入規則會根據產品的需(心)求(情)頻繁變化,因此咱們最開始使用了兩個變量進行標記:數組
const news = [1, 3, 5, 6, 7, 9, 10, 11]; const ads = [2, 4, 8, 12];
當位置發生變化的時候咱們就須要同時對兩個變量進行修改,這樣致使了維護上的成本。因此我想了一個辦法,廣告的位置標記爲 1,文章的位置標記爲 0,使用純二進制的形式來表示個記錄,這樣子就變成了:promise
+---+---+---+ | 0 | 1 | 0 | +---+---+---+ | 1 | 0 | 0 | +---+---+---+ | 0 | 1 | 0 | +---+---+---+ | 0 | 0 | 1 | +---+---+---+ 1 011 010 100 010 001 // 首位爲常量 1 // 2-4 位記錄一行多少條 // 後續按照新聞和廣告的位置進行記錄
最後咱們使用一個變量 0b1011010100010001
就完成了兩種信息的記錄。這樣作將不少數據集成在了一塊兒解決了咱們以前的問題,可是它帶來了新的問題,你們也能夠看到註釋中按照空格劈開的話你們看的還比較明白,可是在段頭將空格去除以後在閱讀程度上就形成了很是大的困難了。而數字分隔符這個語法糖正好就能解決這個問題,0b1_011_010_100_010_001
這樣閱讀起來就好不少了。
雖然在大部分的場景 async/await 均可以了,可是很差意思 Promise 有些場景仍是不可替代的。Promsie.all()
和 Promise.race()
就是這種特別的存在。而 Promise.allSettled()
和 Promise.any()
則是新增長的方法, 相較於它們的前輩,這倆擁有忽略錯誤達到目的的特性。
咱們以前有一個需求,是須要將文件安全的存儲在一個存儲服務中,爲了災備咱們其實有兩個 S3,一個 HBase 還有本地存儲。因此每次都須要諸如如下的邏輯:
for(const service of services) { const result = await service.upload(file); if(result) break; }
但其實我並不關心錯誤,個人目的是隻要保證有一個服務最終能執行成功便可,因此 Promise.any()
其實就能夠解決這個問題。
await Promise.any( services.map(service => service.upload(file)) );
Promise.allsettled()
和 Promise.any()
的引入豐富了 Promise 更多的可能性。說不定之後還會增長更多的特性,例如 Promise.try()
, Promise.some()
, Promise.reduce()
...
WeakRef 這個新類型我最開始是不太理解的,畢竟我總感受 Chrome 已經長大了,確定會本身處理垃圾了。然而事情並無我想的那麼簡單,咱們知道 JS 的垃圾回收主要有「標記清除」和「引用計數」兩種方法。引用計數是隻要變量多一次引用則計數加 1,少一次引用則計數減 1,當引用爲 0 時則表示你已經沒有利用價值了,去垃圾站吧!
在 WeakRef 以前其實已經有兩個相似的類型存在了,那就是 WeakMap 和 WeakSet。以 WeakMap 爲例,它規定了它的 Key 必須是對象,並且它的對象都是弱引用的。舉個例子:
//map.js function usageSize() { const used = process.memoryUsage().heapUsed; return Math.round(used / 1024 / 1024 * 100) / 100 + 'M'; } global.gc(); console.log(usageSize()); // ≈ 3.23M let arr = new Array(5 * 1024 * 1024); const map = new Map(); map.set(arr, 1); global.gc(); console.log(usageSize()); // ≈ 43.22M arr = null; global.gc(); console.log(usageSize()); // ≈ 43.23M
//weakmap.js function usageSize() { const used = process.memoryUsage().heapUsed; return Math.round(used / 1024 / 1024 * 100) / 100 + 'M'; } global.gc(); console.log(usageSize()); // ≈ 3.23M let arr = new Array(5 * 1024 * 1024); const map = new WeakMap(); map.set(arr, 1); global.gc(); console.log(usageSize()); // ≈ 43.22M arr = null; global.gc(); console.log(usageSize()); // ≈ 3.23M
分別執行 node --expose-gc map.js
和 node --expose-gc weakmap.js
就能夠發現區別了。在 arr 和 Map 中都保留了數組的強引用,因此在 Map 中簡單的清除 arr 變量內存並無獲得釋放,由於 Map 還存在引用計數。而在 WeakMap 中,它的鍵是弱引用,不計入引用計數中,因此當 arr 被清除以後,數組會由於引用計數爲0而被回收掉。
正如分享中所說,WeakMap 和 WeakSet 足夠好,可是它要求鍵必須是對象,在某些場景上不太試用。因此他們暴露了更方便的 WeakRef 類型。在 Python 中也存在 WeakRef 類型,乾的事情比較相似。其實咱們主要注意 WeakRef 的引用是不計引用計數的,就好理解了。例如 MDN 中所說的引用計數沒辦法清理的循環引用問題:
function f(){ var o = {}; var o2 = {}; o.a = o2; // o 引用 o2 o2.a = o; // o2 引用 o return "azerty"; } f();
若是試用 WeakRef 來改寫,因爲 WeakRef 不計算引用計數,因此計數一直爲 0,在下一次回收中就會被正常回收了。
function f() { var o = new WeakRef({}); var o2 = o; o.a = o2; return "azerty"; } f();
在以前的一個多進程需求中,咱們須要將子進程中的數據發送到主進程中,咱們使用的方式是這樣寫的:
const metric = 'event'; global.DATA[metric] = {}; process.on(metric, () => { const data = global.DATA[metric]; delete global.DATA[metric]; return data; });
代碼就看着比較怪,因爲 global.DATA[metric]
做爲強引用,若是直接在事件中 return global.DATA[metric]
的話,因爲存在引用計數,那麼這個全局變量是一直佔用內存的。此時若是使用 WeakRef 改寫一下就能夠減小 delete
的邏輯了。
const metric = 'event'; global.DATA[metric] = new WeakRef({}); process.on(metric, () => { const ref = global.DATA[metric]; if(ref !== undefined) { return ref.deref(); } return ref; });
除了我上面講的幾個特性以外,還有不少其餘的特性也很是一顆賽艇。例如 String.matchAll()
讓咱們作屢次匹配的時候不再用寫 while 了!Intl 本地化類的支持,讓咱們能夠早日拋棄 moment.js,特別是 RelativeTimeFormat
類真是解放了咱們的生產力,不過目前接口的配置彷佛比較定製化,不知道後續的細粒度需求支持狀況會如何。
參考資料: