摘要:在和中都保留了數(shù)組的強(qiáng)引用,所以在中簡單的清除變量內(nèi)存并沒有得到釋放,因?yàn)檫€存在引用計(jì)數(shù)。而在中,它的鍵是弱引用,不計(jì)入引用計(jì)數(shù)中,所以當(dāng)被清除之后,數(shù)組會(huì)因?yàn)橐糜?jì)數(shù)為而被回收掉。其實(shí)我們主要注意的引用是不計(jì)引用計(jì)數(shù)的,就好理解了。
前幾天 Google IO 上 V8 團(tuán)隊(duì)為我們分享了《What"s New in JavaScript》主題,分享的語速很慢推薦大家可以都去聽聽就當(dāng)鍛煉下聽力了??赐曛笪艺砹艘粋€(gè)文字版幫助大家快速了解分享內(nèi)容,嘉賓主要是分享了以下幾點(diǎn):
JS 解析快了 2 倍
async 執(zhí)行快了 11 倍
平均減少了 20% 的內(nèi)存使用
class fileds 可以直接在 class 中初始化變量不用寫在 constructor 里
私有變量前綴
string.matchAll 用來做正則多次匹配
numeric seperator 允許我們?cè)趯憯?shù)字的時(shí)候使用 _ 作為分隔符提高可讀性
bigint 新的大數(shù)字類型支持
Intl.NumberFormat 本地化格式化數(shù)字顯示
Array.prototype.flat(), Array.prototype.flatMap() 多層數(shù)組打平方法
Object.entries() 和 Object.fromEntries() 快速對(duì)對(duì)象進(jìn)行數(shù)組操作
globalThis 無環(huán)境依賴的全局 this 支持
Array.prototype.sort() 的排序結(jié)果穩(wěn)定輸出
Intl.RelativeTimeFormat(), Intl.DateTimeFormat() 本地化顯示時(shí)間
Intl.ListFormat() 本地化顯示多個(gè)名詞列表
Intl.locale() 提供某一本地化語言的各種常量查詢
頂級(jí) await 無需寫 async 的支持
Promise.allSettled() 和 Promise.any() 的增加豐富 Promise 場(chǎng)景
WeakRef 類型用來做部分變量弱引用減少內(nèi)存泄露
Async 執(zhí)行比之前快了11倍開場(chǎng)就用 11x faster 數(shù)字把大家驚到了,也有很多同學(xué)好奇到底是怎么做到的。其實(shí)這個(gè)優(yōu)化并不是最近做的,去年11月的時(shí)候 V8 團(tuán)隊(duì)就發(fā)了一篇文章 《Faster async functions and promises》,這里面就非常詳盡的講述了如何讓 async/await 優(yōu)化到這個(gè)速度的,其主要?dú)w功于以下三點(diǎn):
TurboFan:新的 JS 編譯器
Orinoco:新的 GC 引擎
Node.js 8 上的一個(gè) await bug
2008年 Chrome 出世,10年 Chrome 引入了 Crankshaft 編譯器,多年后的今天這員老將已經(jīng)無法滿足現(xiàn)有的優(yōu)化需求,畢竟當(dāng)時(shí)的作者也未曾料想到前端的世界會(huì)發(fā)展的這么快。關(guān)于為何使用 TurboFan 替換掉 Crankshaft,大家可以看看《Launching Ignition and TurboFan》,原文中是這么說的:
Crankshaft 僅支持優(yōu)化 JavaScript 的一部分特性。它并沒有通過結(jié)構(gòu)化的異常處理來設(shè)計(jì)代碼,即代碼塊并沒有通過try、catch、finally等關(guān)鍵字劃分。另外由于為每一個(gè)新的特性Cranksshaft都將要做九套不同的框架代碼適應(yīng)不同的平臺(tái),因此在適配新的Javascript語言特性也很困難。還有Crankshaft框架代碼的設(shè)計(jì)也限制優(yōu)化機(jī)器碼的擴(kuò)展。盡管V8引擎團(tuán)隊(duì)為每一套芯片架構(gòu)維護(hù)超過一萬行代碼,Crankshaft也不過為Javascript擠出一點(diǎn)點(diǎn)性能。
via:《Javascript是如何工作的:V8引擎的內(nèi)核Ignition和TurboFan》
而 TurboFan 則提供了更好的架構(gòu),能夠在不修改架構(gòu)的情況下添加新的優(yōu)化特性,這為面向未來優(yōu)化 JavaScript 語言特性提供了很好的架構(gòu)支持,能讓團(tuán)隊(duì)花費(fèi)更少的時(shí)間在做處理不同平臺(tái)的特性和編碼上。從原文的數(shù)據(jù)對(duì)比中就可以看到,僅僅是換了個(gè)編譯器優(yōu)化就在 8 倍左右了…… 給 V8 的大佬們跪下了。
而 Orinoco 新的 GC 引擎則是使用多帶帶線程異步去處理,讓其不影響 JS 主線程的執(zhí)行。至于最后說的 async/await 的 BUG 則是讓 V8 團(tuán)隊(duì)引發(fā)思考,將 async/await 原本基于 3 個(gè) Promise 的實(shí)現(xiàn)減少成 2 個(gè),最終減少成 1 個(gè)!最后達(dá)到了寫 async/await 比直接寫 Promise 還要快的結(jié)果。
我們知道 await 后面跟的是 Promise 對(duì)象,但是即使不是 Promise JS 也會(huì)幫我們將其包裝成 Promise。而在 V8 引擎中,為了實(shí)現(xiàn)這個(gè)包裝,至少需要一個(gè) Promise,兩個(gè)微任務(wù)過程。這個(gè)在本身已經(jīng)是 Promise 的情況下就有點(diǎn)虧大發(fā)了。而為了實(shí)現(xiàn) async/await 在 await 結(jié)束之后要重新原函數(shù)的上下文并提供 await 之后的結(jié)果,規(guī)范定義此時(shí)還需要一個(gè) Promise,這個(gè)在 V8 團(tuán)隊(duì)看來是沒有必要的,所以他們建議規(guī)范去除這個(gè)特性。
最后的最后,官方還建議我們:多使用 async/await 而不是手寫 Promise 代碼,多使用 JavaScript 引擎提供的 Promise 而不是自己去實(shí)現(xiàn)。
Numeric Seperator隨著 Babel 的出現(xiàn),JS 的語法糖簡直不要太多,Numeric Seperator 算是一個(gè)。簡單的說來它為我們手寫數(shù)字的時(shí)候提供給了分隔符的支持,讓我們?cè)趯懘髷?shù)字的時(shí)候具有可讀性。
其實(shí)是個(gè)很簡單的語法糖,為什么我會(huì)多帶帶列出來說呢,主要是因?yàn)樗媒鉀Q了我之前一個(gè)實(shí)現(xiàn)的痛點(diǎn)。我有一個(gè)需求是一堆文章數(shù)據(jù),我要按照產(chǎn)品給的規(guī)則去插入廣告。如圖非紅框是文章,紅框處是廣告。由于插入規(guī)則會(huì)根據(jù)產(chǎn)品的需(心)求(情)頻繁變化,所以我們最開始使用了兩個(gè)變量進(jìn)行標(biāo)記:
const news = [1, 3, 5, 6, 7, 9, 10, 11]; const ads = [2, 4, 8, 12];
當(dāng)位置發(fā)生變化的時(shí)候我們就需要同時(shí)對(duì)兩個(gè)變量進(jìn)行修改,這樣導(dǎo)致了維護(hù)上的成本。所以我想了一個(gè)辦法,廣告的位置標(biāo)記為 1,文章的位置標(biāo)記為 0,使用純二進(jìn)制的形式來表示個(gè)記錄,這樣子就變成了:
+---+---+---+ | 0 | 1 | 0 | +---+---+---+ | 1 | 0 | 0 | +---+---+---+ | 0 | 1 | 0 | +---+---+---+ | 0 | 0 | 1 | +---+---+---+ 1 011 010 100 010 001 // 首位為常量 1 // 2-4 位記錄一行多少條 // 后續(xù)按照新聞和廣告的位置進(jìn)行記錄
最后我們使用一個(gè)變量 0b1011010100010001 就完成了兩種信息的記錄。這樣做將很多數(shù)據(jù)集成在了一起解決了我們之前的問題,但是它帶來了新的問題,大家也可以看到注釋中按照空格劈開的話大家看的還比較明白,但是在段頭將空格去除之后在閱讀程度上就造成了非常大的困難了。而數(shù)字分隔符這個(gè)語法糖正好就能解決這個(gè)問題,0b1_011_010_100_010_001 這樣閱讀起來就好很多了。
Promise雖然在大部分的場(chǎng)景 async/await 都可以了,但是不好意思 Promise 有些場(chǎng)景還是不可替代的。Promsie.all() 和 Promise.race() 就是這種特別的存在。而 Promise.allSettled() 和 Promise.any() 則是新增加的方法, 相較于它們的前輩,這倆擁有忽略錯(cuò)誤達(dá)到目的的特性。
我們之前有一個(gè)需求,是需要將文件安全的存儲(chǔ)在一個(gè)存儲(chǔ)服務(wù)中,為了災(zāi)備我們其實(shí)有兩個(gè) S3,一個(gè) HBase 還有本地存儲(chǔ)。所以每次都需要諸如以下的邏輯:
for(const service of services) { const result = await service.upload(file); if(result) break; }
但其實(shí)我并不關(guān)心錯(cuò)誤,我的目的是只要保證有一個(gè)服務(wù)最終能執(zhí)行成功即可,所以 Promise.any() 其實(shí)就可以解決這個(gè)問題。
await Promise.any( services.map(service => service.upload(file)) );
Promise.allsettled() 和 Promise.any() 的引入豐富了 Promise 更多的可能性。說不定以后還會(huì)增加更多的特性,例如 Promise.try(), Promise.some(), Promise.reduce() ...
WeakRefWeakRef 這個(gè)新類型我最開始是不太理解的,畢竟我總感覺 Chrome 已經(jīng)長大了,肯定會(huì)自己處理垃圾了。然而事情并沒有我想的那么簡單,我們知道 JS 的垃圾回收主要有“標(biāo)記清除”和“引用計(jì)數(shù)”兩種方法。引用計(jì)數(shù)是只要變量多一次引用則計(jì)數(shù)加 1,少一次引用則計(jì)數(shù)減 1,當(dāng)引用為 0 時(shí)則表示你已經(jīng)沒有利用價(jià)值了,去垃圾站吧!
在 WeakRef 之前其實(shí)已經(jīng)有兩個(gè)類似的類型存在了,那就是 WeakMap 和 WeakSet。以 WeakMap 為例,它規(guī)定了它的 Key 必須是對(duì)象,而且它的對(duì)象都是弱引用的。舉個(gè)例子:
//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
分別執(zhí)行 node --expose-gc map.js 和 node --expose-gc weakmap.js 就可以發(fā)現(xiàn)區(qū)別了。在 arr 和 Map 中都保留了數(shù)組的強(qiáng)引用,所以在 Map 中簡單的清除 arr 變量內(nèi)存并沒有得到釋放,因?yàn)?Map 還存在引用計(jì)數(shù)。而在 WeakMap 中,它的鍵是弱引用,不計(jì)入引用計(jì)數(shù)中,所以當(dāng) arr 被清除之后,數(shù)組會(huì)因?yàn)橐糜?jì)數(shù)為0而被回收掉。
正如分享中所說,WeakMap 和 WeakSet 足夠好,但是它要求鍵必須是對(duì)象,在某些場(chǎng)景上不太試用。所以他們暴露了更方便的 WeakRef 類型。在 Python 中也存在 WeakRef 類型,干的事情比較類似。其實(shí)我們主要注意 WeakRef 的引用是不計(jì)引用計(jì)數(shù)的,就好理解了。例如 MDN 中所說的引用計(jì)數(shù)沒辦法清理的循環(huán)引用問題:
function f(){ var o = {}; var o2 = {}; o.a = o2; // o 引用 o2 o2.a = o; // o2 引用 o return "azerty"; } f();
如果試用 WeakRef 來改寫,由于 WeakRef 不計(jì)算引用計(jì)數(shù),所以計(jì)數(shù)一直為 0,在下一次回收中就會(huì)被正?;厥樟恕?/p>
function f() { var o = new WeakRef({}); var o2 = o; o.a = o2; return "azerty"; } f();
在之前的一個(gè)多進(jìn)程需求中,我們需要將子進(jìn)程中的數(shù)據(jù)發(fā)送到主進(jìn)程中,我們使用的方式是這樣寫的:
const metric = "event"; global.DATA[metric] = {}; process.on(metric, () => { const data = global.DATA[metric]; delete global.DATA[metric]; return data; });
代碼就看著比較怪,由于 global.DATA[metric] 作為強(qiáng)引用,如果直接在事件中 return global.DATA[metric] 的話,由于存在引用計(jì)數(shù),那么這個(gè)全局變量是一直占用內(nèi)存的。此時(shí)如果使用 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; });后記
除了我上面講的幾個(gè)特性之外,還有很多其他的特性也非常一顆賽艇。例如 String.matchAll() 讓我們做多次匹配的時(shí)候再也不用寫 while 了!Intl 本地化類的支持,讓我們可以早日拋棄 moment.js,特別是 RelativeTimeFormat 類真是解放了我們的生產(chǎn)力,不過目前接口的配置似乎比較定制化,不知道后續(xù)的細(xì)粒度需求支持情況會(huì)如何。
參考資料:
《ES proposal: numeric separators》
《內(nèi)存管理》
《ES6 系列之 WeakMap》
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/109419.html
摘要:一般會(huì)每隔周發(fā)布一次主版本。于是我就制作了中文字幕并將視頻發(fā)到了。站地址中文字幕在開發(fā)者博客上,文章也已經(jīng)在月份發(fā)布了,文中提到視頻會(huì)在年月底發(fā)布,到時(shí)候我會(huì)繼續(xù)制作中文字幕。 大家好,這是代碼之聲(codefm)第一期,今天給大家?guī)淼氖?Whats New In DevTools (Chrome 63)。 Chrome 一般會(huì)每隔 6 周發(fā)布一次主版本。?目前 Chrome 的最新...
摘要:一般會(huì)每隔周發(fā)布一次主版本。于是我就制作了中文字幕并將視頻發(fā)到了。站地址中文字幕在開發(fā)者博客上,文章也已經(jīng)在月份發(fā)布了,文中提到視頻會(huì)在年月底發(fā)布,到時(shí)候我會(huì)繼續(xù)制作中文字幕。 大家好,這是代碼之聲(codefm)第一期,今天給大家?guī)淼氖?Whats New In DevTools (Chrome 63)。 Chrome 一般會(huì)每隔 6 周發(fā)布一次主版本。?目前 Chrome 的最新...
摘要:舉例來說即便某個(gè)失敗了,也不會(huì)導(dǎo)致的發(fā)生,這樣在不在乎是否有項(xiàng)目失敗,只要拿到都結(jié)束的信號(hào)的場(chǎng)景很有用。對(duì)于則稍有不同只要有子項(xiàng),就會(huì)完成,哪怕第一個(gè)了,而第二個(gè)了,也會(huì),而對(duì)于,這種場(chǎng)景會(huì)直接。 1. 引言 本周精讀的內(nèi)容是:Google I/O 19。 2019 年 Google I/O 介紹了一些激動(dòng)人心的 JS 新特性,這些特性有些已經(jīng)被主流瀏覽器實(shí)現(xiàn),并支持 polyfill...
摘要:一個(gè)表示編譯器檢測(cè)到一個(gè)無效的引用值。在實(shí)際情況中,往往是在獲取一個(gè)未被賦值的引用時(shí)被拋出。任何一個(gè)函數(shù)上下文都有一個(gè)被稱為活動(dòng)對(duì)象的變量對(duì)象。沒有找到的話,就會(huì)認(rèn)為引用名沒有基礎(chǔ)值并拋出的錯(cuò)誤。下沒有下的屬性僅存在于被啟動(dòng)的情況下。 和其他語言相比,javascript中的對(duì)于undefined的理解還是有點(diǎn)讓人困惑的。特別是試著理解ReferenceErrors錯(cuò)誤(x is no...
摘要:彈出的就是,歲。值得注意的是,和都是改變上下文中的并立即執(zhí)行這個(gè)函數(shù),方法改變了指向之后會(huì)返回一個(gè)函數(shù),可以隨時(shí)調(diào)用。和作用完全一樣,只是傳參的方式不一樣。以上,有錯(cuò)希望各位大神斧正。 apply bind call這三個(gè)方法,作用都是改變當(dāng)前使用該方法的對(duì)象的this指向。但三個(gè)方法還是有一些區(qū)別,先說說共同點(diǎn)。 window.person = { name: mice...
閱讀 2964·2021-11-23 09:51
閱讀 1677·2021-10-15 09:39
閱讀 1070·2021-08-03 14:03
閱讀 2900·2019-08-30 15:53
閱讀 3448·2019-08-30 15:52
閱讀 2502·2019-08-29 16:17
閱讀 2804·2019-08-29 16:12
閱讀 1661·2019-08-29 15:26