大家好政基,我是這波能反殺瓦糕,一名光榮的十年老前端。
這兩天腋么,我已經(jīng)麻了。
當(dāng)我準(zhǔn)備集中精力完成《React 知命境》的時(shí)候亥揖,一個(gè)不起眼的小問(wèn)題珊擂,幽幽的復(fù)現(xiàn)在我的眼前。
我的第一反應(yīng)费变,這么簡(jiǎn)單的問(wèn)題摧扇,想要用的時(shí)候就用唄。
但是... ... 不對(duì)挚歧,在我模糊的印象中扛稽,這個(gè)人能力挺強(qiáng),常年看我的文章滑负,對(duì)基礎(chǔ)的研究比較深入在张,不會(huì)問(wèn)那么隨便的問(wèn)題,搞不好我這樣敷衍了之后矮慕,還會(huì)被引出下一個(gè)問(wèn)題帮匾,我得回答專業(yè)一點(diǎn),穩(wěn)住我十年老油子的高大人設(shè)才行痴鳄。
根據(jù)我的預(yù)判瘟斜,她一定是想問(wèn)什么時(shí)候用 Object
,什么時(shí)候用 Map
,在 JavaScript
的使用者中螺句,有此疑問(wèn)的不在少數(shù)虽惭。于是我就開(kāi)始在記憶中搜索關(guān)于這兩個(gè)對(duì)象的區(qū)別。
Object
和 Map
都可以存儲(chǔ)鍵值對(duì)蛇尚。
Object
的 key
值只能是數(shù)字芽唇、字符串,symbol佣蓉。Map
的key值可以是任意數(shù)據(jù)類型披摄。
Map
是可迭代對(duì)象,Object
不可以迭代勇凭。
Map
會(huì)記錄屬性的寫(xiě)入順序疚膊,Object
不會(huì)記錄寫(xiě)入的先后順序甚至還有可能會(huì)排序。
Map
有 size 屬性虾标,而 Object 沒(méi)有寓盗。
那什么時(shí)候用 Map
比 Object
更合適呢?
額... ... 糟糕璧函,想不起來(lái)傀蚌!
CPU 單線程運(yùn)轉(zhuǎn)了五分鐘,還是想不起來(lái)蘸吓。不過(guò)沒(méi)關(guān)系善炫,我還有萬(wàn)能的百度,我輸入關(guān)鍵字库继,開(kāi)始搜索箩艺,我很快就能得到答案。
果然百度大法好宪萄,搜索結(jié)果里有大量的文章在分析他們艺谆,頭一條是掘金的一篇譯文,一看就比較靠譜拜英,于是滿懷期待的點(diǎn)進(jìn)去静汤,開(kāi)始閱讀這篇文章。
文章在分析了大量的各自的特點(diǎn)之后居凶,終于看到了我想要的內(nèi)容虫给,Map 的應(yīng)用場(chǎng)景。
然而讀著讀著侠碧,好像有點(diǎn)不太對(duì)勁狰右。更多的還是一些特性的分析,例如 Map
的刪除操作性能更好舆床,存儲(chǔ)大數(shù)更合適棋蚌,并沒(méi)有介紹任何具體的場(chǎng)景嫁佳。
而且還出現(xiàn)了一些我不太認(rèn)同的小疑問(wèn)。
好吧谷暮,這篇文章沒(méi)有我找到的答案蒿往,如法炮制,我開(kāi)始閱讀別的文章湿弦。
然而... 大多數(shù)都是雷同的信息瓤漏。
我依稀記得之前使用 Map 解決過(guò)一個(gè)非常棒的案例,可就是想不起來(lái)颊埃,加上搜索無(wú)望蔬充,我不免有些心急。
時(shí)間已經(jīng)不知不覺(jué)過(guò)去了 10 分鐘班利,我還沒(méi)有回復(fù)那個(gè)尊稱我為“波神”的小妹妹饥漫。我已經(jīng)感受到了,我的大佬人設(shè)正在逐漸崩塌罗标。
對(duì)哦庸队,我可太蠢了,既然我之前使用過(guò)闯割,把項(xiàng)目代碼拿出來(lái)搜索一下不就找到了嗎彻消?
看到了希望的曙光,我進(jìn)入了修改緊急 bug 的高效狀態(tài)宙拉。迅速打開(kāi) vs code
宾尚,打開(kāi)我的項(xiàng)目代碼,全局搜索 new Map
谢澈。
哈哈煌贴,果然找到了大量使用 Map
的代碼。
可是當(dāng)我點(diǎn)開(kāi)代碼之后澳化,才發(fā)現(xiàn).... 居然沒(méi)有熟悉感。全都不是我想要的場(chǎng)景稳吮。
這樣的情況缎谷,用 {}
不更簡(jiǎn)單嗎?
這肯定不是我寫(xiě)的代碼灶似。
真正的大師列林,永遠(yuǎn)懷著一顆學(xué)徒的心。
我實(shí)在想不起來(lái)以前那個(gè)非常適合用 Map 的場(chǎng)景了酪惭,搜索找不到希痴,代碼也找不到,好在我還有幾個(gè)大廠大佬眾多春感,且非称龃矗活躍的技術(shù)群虏缸。
于是我問(wèn).
我再問(wèn).
這個(gè)問(wèn)題在幾個(gè)群都引發(fā)了激烈的討論,我就像一個(gè)小白瘋狂的吸收著大佬們的知識(shí)嫩实。大佬之間的討論就是不一樣刽辙,很快我們就撇開(kāi)了毫無(wú)意義的表面特性,開(kāi)始聊起了性能甲献。
有個(gè)字節(jié)的大佬拋出一個(gè)觀點(diǎn)宰缤,他說(shuō),對(duì)象的讀取沒(méi)有 Map 快晃洒,所以他幾乎都是能用 Map 的地方就會(huì)用 Map慨灭。
另一個(gè)大佬認(rèn)為在速度上 Map 比 Object 并沒(méi)有明顯優(yōu)勢(shì),在刪除屬性時(shí) Map 表現(xiàn)更好一點(diǎn)球及。
我又想到了剛才看的掘金的文章氧骤,說(shuō)是 Object 的讀寫(xiě)速度更快,幾個(gè)結(jié)論說(shuō)法不一桶略,于是討論陷入了驗(yàn)證階段语淘。
如果這個(gè)事情能夠得到論證的話,那么「能用 Map 的地方就使用 Map」 就是一個(gè)非常完美的答案际歼。
為了證明這個(gè)事情惶翻,我開(kāi)始考慮一個(gè)事情,Object 在內(nèi)存中到底是如何存儲(chǔ)的鹅心?Map 又是如何存儲(chǔ)的呢吕粗?
我依稀記得 V8 對(duì) Object 的處理是有優(yōu)化手段的,但是年代久遠(yuǎn)記不清晰了旭愧,于是有了新的方向颅筋,我再次踏上了尋找資料的征途。再次祭出百度输枯。
果然不出我所料议泵。
V8 對(duì)對(duì)象屬性的存儲(chǔ)結(jié)構(gòu)并沒(méi)有表面上那么簡(jiǎn)單,有特殊的處理桃熄。
在掘金和知乎的文章里先口,我找到這個(gè)圖。
一個(gè)對(duì)象里有快屬性和快屬性的區(qū)分瞳收。當(dāng)屬性數(shù)量較少時(shí)「in-object properties」碉京,或者 key 為數(shù)字類型「elements」,此時(shí)會(huì)采用快屬性螟深,快屬性使用線性結(jié)構(gòu)存儲(chǔ)谐宙。所以讀取屬性的速度是非常快的界弧。當(dāng)屬性變多凡蜻,為了確保新增和刪除的效率搭综,此時(shí)會(huì)啟用慢屬性「properties」,采用詞典鍵值對(duì)的方式存儲(chǔ)屬性內(nèi)容咽瓷。
我知道设凹,很多人看到這里,肯定會(huì)疑問(wèn)什么是線性結(jié)構(gòu)茅姜,什么是非線性結(jié)構(gòu)闪朱,哈哈哈,還好我沒(méi)有疑問(wèn)钻洒。
也就是說(shuō)奋姿,從基礎(chǔ)理論上來(lái)看,Object 會(huì)因?yàn)閷傩詳?shù)量上分為兩個(gè)階段素标,從而解決 Object 的讀寫(xiě)問(wèn)題称诗,而且,V8 還為 Object 創(chuàng)建了隱藏類用于記錄每個(gè)屬性的偏移量头遭,也就意味著寓免,Object 的讀寫(xiě)不會(huì)太慢。
這個(gè)界限有的文章說(shuō)是10個(gè)计维,但是我使用開(kāi)發(fā)者工具的 Memery 記錄的 snapshot 驗(yàn)證的結(jié)果不是這樣
那么 Map 的存儲(chǔ)在內(nèi)存中又是什么結(jié)構(gòu)呢袜香?
哈哈,這個(gè)我知道鲫惶!
散列表 + 鏈表 + 紅黑樹(shù) = hashMap蜈首。
我突然就悟了。
也就是說(shuō)欠母,如果屬性數(shù)量偏小的情況下欢策,讀寫(xiě)速度上,Object 和 Map 應(yīng)該不會(huì)有太大的差別赏淌。而只有數(shù)據(jù)量非常大的時(shí)候踩寇,才會(huì)逐漸體現(xiàn)出來(lái)差別。那么這個(gè)數(shù)據(jù)量大六水,到底要達(dá)到什么程度呢俺孙?我也不確定。
寫(xiě)個(gè)案例缩擂,驗(yàn)證一下鼠冕。
首先驗(yàn)證一下寫(xiě)入的時(shí)間成本添寺。
// 先定義一個(gè)數(shù)量上限
const up = 9999
var mt1 = performance.now()
var map = new Map()
for(var i = 0; i < up; i++) {
map.set(`f${i}`, {a: i, children: { a: i }})
}
console.log(` Map: `, performance.now() - mt1)
var ot1 = performance.now()
var obj = {}
for (var i = 0; i < up; i++) {
obj[`f${i}`] = {b: i, children: {a: i}}
}
console.log('Object: ', performance.now() - ot1)
刷新了 20 多次胯盯,基本上都在 5 ~ 10 ms 之間波動(dòng)。時(shí)間上誰(shuí)高誰(shuí)低沒(méi)有明確的差別计露。不過(guò) Object 比 Map 耗時(shí)更短的次數(shù)會(huì)多一點(diǎn)博脑。這符合我的預(yù)期憎乙。
接下來(lái)我組建調(diào)高 up 變量的值。繼續(xù)驗(yàn)證叉趣。當(dāng)我把 up 的值設(shè)置為 99999 時(shí)泞边,耗時(shí)上依然沒(méi)有明顯的差別。
當(dāng)我把 up 的值設(shè)置為 499999
時(shí)疗杉,Object 的寫(xiě)入速度才開(kāi)始穩(wěn)定的比 Map 耗時(shí)更長(zhǎng)阵谚。
好家伙,我從來(lái)不會(huì)維護(hù)這么大的數(shù)據(jù)量在項(xiàng)目中烟具。
接下來(lái)梢什,我又依次驗(yàn)證的讀取速度和刪除速度。
在刪除上朝聋,我把 up 的值設(shè)置為 199999
嗡午,Object 的刪除耗時(shí)才會(huì)穩(wěn)定的比 Map 慢。
// 刪除
for(var i = 0; i < up; i++) {
map.delete(`f${i}`)
}
在讀取速度上冀痕,up 的值為 159999
時(shí) Object 的讀取速度會(huì)穩(wěn)定比 Map 慢荔睹。
// 訪問(wèn)
for (var i = 0; i < up; i++) {
map.get(`f${i}`)
}
所以,在性能的表現(xiàn)上言蛇,新增僻他、刪除、讀取的速度猜极,在數(shù)量非常少時(shí)中姜,Object 的表現(xiàn)可能會(huì)稍微好那么一點(diǎn)點(diǎn)點(diǎn),甚至不太明顯能感知得出來(lái)跟伏。
而在數(shù)量非常大的時(shí)候丢胚,Map 的表現(xiàn)會(huì)比 Object 好∈馨猓可是這種程度的數(shù)量携龟,我想很難在項(xiàng)目中把數(shù)據(jù)維護(hù)到這種程度。
驗(yàn)證結(jié)果讓我居然神奇的發(fā)現(xiàn)勘高,上面兩位大佬不一樣的觀點(diǎn)峡蟋,居然都說(shuō)的過(guò)去。
能用 Map 就用 Map 华望,沒(méi)什么毛病蕊蝗。Object 也沒(méi)有比 Map 有什么明顯的速度優(yōu)勢(shì)。
當(dāng)我做完驗(yàn)證回過(guò)頭來(lái)看群消息的時(shí)候赖舟,另外一個(gè)群的大佬提供了一個(gè)非常牛逼的應(yīng)用場(chǎng)景蓬戚。那就是策略模式的封裝。
膜拜宾抓!
策略模式通常情況下都是一個(gè)鍵值對(duì)應(yīng)一個(gè)規(guī)則子漩。但是豫喧!在某些特殊場(chǎng)景下,會(huì)出現(xiàn)多個(gè)鍵值對(duì)應(yīng)一個(gè)規(guī)則的情況幢泼。這個(gè)時(shí)候紧显,Map 就有了用武之地。Map 支持正則表達(dá)式作為 key 值缕棵,這樣孵班,使用 Map 就可以存儲(chǔ)多對(duì)一的匹配規(guī)則。
折騰了一天招驴,我苦逼的發(fā)現(xiàn)重父,我終于想起來(lái)我之前用 Map 實(shí)現(xiàn)的那個(gè)應(yīng)用場(chǎng)景是什么了。
( |||)
那就是聊天列表和聊天內(nèi)容的實(shí)現(xiàn)忽匈。這個(gè)場(chǎng)景完美符合了 Map 的特性房午。
聊天內(nèi)容列表因?yàn)橐彺婧芏鄡?nèi)容,數(shù)據(jù)量夠大丹允,并且郭厌,聊天是一個(gè)頻繁變動(dòng)的場(chǎng)景。聊天列表有新消息就會(huì)重新排序雕蔽,聊天內(nèi)容也會(huì)頻繁的插入新的消息折柠,特別是群聊,對(duì)聊天內(nèi)容的順序也有嚴(yán)格的要求批狐。
數(shù)據(jù)量大扇售、頻繁寫(xiě)入、排序嚣艇、對(duì)寫(xiě)入順序有嚴(yán)格的要求承冰,這個(gè)場(chǎng)景使用 Map 來(lái)管理數(shù)據(jù)再合適不過(guò)了。
太棒了食零。有了這些東西困乒,我終于松了一口氣》∫ィ可以回復(fù)她了娜搂。
可是,當(dāng)我點(diǎn)開(kāi)聊天窗口的時(shí)候... ...
我是這波能反殺吱抚,關(guān)注我百宇,解鎖更多... ... 哎,算了...