<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在 JavaScript 中選擇 Object 和 Map 的缺失指南
在 JavaScript 中,物件很方便。它們使我們能夠輕鬆地將多條資料組合在一起。在 ES6 之後,我們為該語言新增了一個新功能 - Map
. 在很多方面,它似乎比Object
更強大,但介面有點笨拙。然而,大多數人在需要雜湊對映時仍然會使用物件,並且只有在他們意識到鍵不能只是他們用例的字串時才切換到使用。因此,在當今的 JavaScript 社群中Map
仍未得到充分利用。
在這篇文章中,我將分解您應該考慮使用Map
more 的所有原因及其效能特徵與基準測試。
在 JavaScript 中,Object 是一個相當寬泛的術語。幾乎所有東西都可以是一個物件,除了兩種底部型別 -null
和undefined
. 在這篇博文中,Object 僅指普通的舊物件,由左大括號{
和右大括號分隔}
。
Object
在作者時已知的屬性/欄位數量固定且有限的記錄,例如設定物件。以及一般一次性使用的任何東西。Map
字典或雜湊對映,其中條目數量可變,更新頻繁,其鍵在作者時可能未知,例如事件發射器。Map
方面確實比Object
鍵更高效,並且它消耗的記憶體比Object
相同大小的要少。將物件用於雜湊對映最明顯的缺點可能是物件只允許字串和符號作為鍵。任何其他型別都將通過toString
將這些方法隱式轉換為字串。
const foo = [] const bar = {} const obj = {[foo]: 'foo', [bar]: 'bar'} console.log(obj) // {"": 'foo', [object Object]: 'bar'}
更重要的是,將物件用於雜湊對映可能會導致混淆和安全隱患。
在 ES6 之前,獲取雜湊對映的唯一方法是建立一個空物件。
const hashMap = {}
但是,在建立後,此物件不再為空。雖然hashMap
是用一個空的物件字面量製作的,但它會自動繼承自Object.prototype
. 這就是為什麼我們可以呼叫像hasOwnProperty
, toString
, constructor
on這樣的方法,hashMap
即使我們從未在物件上明確定義這些方法。
由於原型繼承,我們現在混合了兩種型別的屬性:存在於物件本身中的屬性,即它自己的屬性,以及存在於原型鏈中的屬性,即繼承的屬性。因此,我們需要額外的檢查(例如hasOwnProperty
)來確保給定的屬性確實是使用者提供的,而不是從原型繼承的。
最重要的是,由於屬性解析機制在 JavaScript 中的工作方式 Object.prototype
,執行時的任何更改都會在所有物件中產生連鎖反應。 這為原型汙染攻擊開啟了大門,這對於大型 JavaScript 應用程式來說可能是一個嚴重的安全問題。
幸運的是,我們可以通過使用來解決這個問題Object.create(null)
,這會生成一個不繼承任何內容的物件Object.prototype
。
當一個物件自己的屬性與其原型上的屬性發生名稱衝突時,它會破壞預期並因此使您的程式崩潰。
例如,我們有一個foo
接受物件的函數:
function foo(obj) { //... for(const key in obj) { if(obj.hasOwnProperty(key)) { } } }
存在可靠性風險obj.hasOwnProperty(key)
:考慮到屬性解析機制在 JavaScript 中的工作方式,如果obj
包含使用者提供的同名屬性,則會hasOwnProperty
隱藏Object.prototype.hasOwnProperty
. 結果,我們不知道在執行時會準確呼叫哪個方法。
可以進行一些防禦性程式設計來防止這種情況。例如,我們可以借用hasOwnProperty
來代替: Object.prototype
function foo(obj) { //... for(const key in obj) { if(Object.prototype.hasOwnProperty.call(obj, key)) { // ... } } }
一種更短的方法可能是在物件文字上呼叫該方法,{}.hasOwnProperty.call(key)
但它仍然很麻煩。這就是為什麼有一個新新增的靜態方法Object.hasOwn
。
Object
沒有提供方便的 API 來獲取大小,即屬性的數量。構成物件大小的因素也有細微差別:
Object.keys()
並獲取其length
.Object.getOwnPropertyNames
來獲取鍵列表並獲取其長度。getOwnPropertySymbols
來顯示符號鍵。或者,您可以使用Reflect.ownKeys
同時獲取字串鍵和符號鍵,無論它是否可列舉。遍歷物件也有類似的複雜性。
我們可以使用良好的舊for...in
迴圈。但它揭示了繼承的可列舉屬性:
Object.prototype.foo = 'bar' const obj = {id: 1} for(const key in obj) { console.log(key) // 'id', 'foo' }
我們不能用for...of
與物件一起使用,因為預設情況下它不是可迭代的,除非我們用Symbol.iterator
在其上顯式定義方法。
我們可以使用Object.keys
,Object.values
和Object.entries
來獲取可列舉的字串鍵(或/和值)列表,然後對其進行迭代,這會引入額外的開銷步驟。
最後,臭名昭著的插入順序沒有得到充分尊重。在大多數瀏覽器中,整數鍵按升序排序並且優先於字串鍵,即使字串鍵插入到整數鍵之前也是如此。
const obj = {} obj.foo = 'first' obj[2] = 'second' obj[1] = 'last' console.log(obj) // {1: 'last', 2: 'second', foo: 'first'}
沒有簡單的方法可以從物件中刪除所有屬性,您必須使用delete
操作符一個一個地刪除每個屬性,這在歷史上被認為是緩慢的。但是我的基準測試表明,它的效能實際上比不上Map.prototype.delete
最後,我們不能依賴點/括號符號來檢查屬性是否存在,因為值本身可以設定為undefined
. 相反,我們必須使用Object.prototype.hasOwnProperty
和 Object.hasOwn
。
const obj = {a: undefined} Object.hasOwn(obj, 'a') // true
ES6 帶來了 Map。它更適合雜湊對映。
首先,與Object
只允許字串和符號作為鍵不同,它Map
支援任何資料型別的鍵。
但是,如果您Map
用於儲存物件的後設資料,那麼您應該使用它WeakMap
來避免記憶體漏失。
但更重要的是,Map
它提供了使用者定義和內建程式資料之間的清晰分離,但代價是額外的檢索條目。
Map
還提供了更好的人體工程學:Map
預設情況下,A 是可迭代的。這意味著您可以使用for...of
輕鬆迭代地圖,並執行諸如使用巢狀解構從地圖中提取第一個條目之類的操作。
const [[firstKey, firstValue]] = map
與Object
相比Map
為各種常見任務提供專用 API:
Map.prototype.has
檢查給定條目的存在,Object.prototype.hasOwnProperty
/Object.hasOwn
在物件上相比不那麼尷尬
Map.prototype.get
返回與提供的鍵關聯的值。人們可能會覺得這比物件上的點表示法或括號表示法更笨拙。然而,它在使用者資料和內建方法之間提供了清晰的分離。
Map.prototype.size
返回 a 中的條目數,Map
它顯然是獲得物件大小所必須執行的操作的贏家。此外,它要快得多。
Map.prototype.clear
刪除 a 中的所有條目,Map
它比運運算元delete
快得多。
在大多數情況下,JavaScript 社群似乎普遍認為Map
比Object
好. 有些人聲稱要通過Object
切換到Map
.
我磨練 Leetcode 的經驗似乎證實了這個信念:Leetcode 將大量資料作為測試用例提供給您的解決方案,如果您的解決方案耗時過長,它就會超時。像這樣的問題只有在你使用Object
時才會超時,而不是在Map
.
但是,我相信只是說“Map
比物件更快”是簡化的。一定有一些細微差別是我想自己找出來的。所以。我構建了一個小應用程式來執行一些基準測試。
該應用程式有一個表格,顯示在Object
和Map
上測量的插入、迭代和刪除速度。
插入和迭代的效能以每秒運算元來衡量。我編寫了一個 util 函數measureFor
,它重複執行目標函數,直到達到指定的最小時間閾值(即duration
UI 上的輸入欄位)。它返回每秒執行此類函數的平均次數。
function measureFor(f, duration) { let iterations = 0; const now = performance.now(); let elapsed = 0; while (elapsed < duration) { f(); elapsed = performance.now() - now; iterations++; } return ((iterations / elapsed) * 1000).toFixed(4); }
至於刪除,我只是要測量使用delete
運運算元從物件中刪除所有屬性所需的時間,並將其與 Map.prototype.delete
相同大小的 Map 的時間進行比較。我可以使用Map.prototype.clear
,但它違背了基準測試的目的,因為我確信它會更快。
在這三個操作中,我更加關注插入,因為它往往是我在日常工作中執行的最常見的操作。對於迭代效能,很難提出一個包羅萬象的基準,因為我們可以在給定物件上執行許多不同的迭代變體。這裡我只測量for ... in
迴圈。
我在這裡使用了三種型別的鍵:
yekwl7caqejth7aawelo4
.123
.Math.random().toString()
,例如0.4024025689756525
。所有的鍵都是隨機生成的,所以我們不會碰到 V8 實現的內聯快取。我還顯式地將整數和數位鍵轉換為字串,然後再將它們新增到物件以避免隱式轉換的開銷。
最後,在基準測試開始之前,還有一個至少 100 毫秒的預熱階段,我們反覆建立新的物件和地圖,這些新物件和地圖會立即被丟棄。
如果你想玩,我把程式碼放在Codesandbox上。
我從100個屬性/條目的Object和Map開始,一直到5000000,讓每種型別的操作持續執行10000ms,看看它們彼此之間的表現如何。以下是我的發現……
為什麼我們在條目數達到 5000000 時停止?
一般來說,當鍵是(非數位)字串時,在所有操作上都Map
優於Object
。
但細微差別在於,當條目數量不是很大(低於 100000)時,Map
插入速度是Object
插入速度的兩倍,但隨著大小增長超過 100000,效能差距開始縮小。
我製作了一些圖表來更好地說明我的發現。
上圖顯示了隨著條目數量的增加(x 軸),插入率如何下降(y 軸)。但是因為 X 軸擴充套件得太寬(從 100 到 1000000),所以很難分辨這兩條線之間的差距。
然後我使用對數刻度來處理資料並製作下面的圖表。
您可以清楚地看出兩條線正在匯合。
我製作了另一個圖表,繪製了Map
與Object
插入速度相關的速度。您可以看到Map
開始時比Object
快. 然後隨著時間的推移,效能差距開始縮小。Map
隨著規模增長到 5000000,最終速度僅快 30%。
但是,我們大多數人在一個物件或對映中永遠不會有超過 100 萬個條目。具有數百或數千個條目的大小. 因此,我們是否應該把它留在那兒,然後全力以赴開始重構我們的程式碼庫Map
?
絕對不會……或者至少沒有期望我們的應用程式會快 2 倍。請記住,我們還沒有探索過其他型別的鍵。讓我們看一下整數鍵。
我特別想對具有整數鍵的物件執行基準測試的原因是 V8 在內部優化了整數索引屬性並將它們儲存在可以線性和連續存取的單獨陣列中。我找不到任何資源來確認它對Map
採用了相同型別的優化。
讓我們首先嚐試 [0, 1000] 範圍內的整數鍵。
正如我所料,這次Object
跑贏大盤。 Object
插入速度比Map
快 65%,迭代速度快 16%。
讓我們擴大範圍,使鍵中的最大整數為 1200。
現在似乎Map
開始比Object
快一點
現在我們只增加了整數鍵的範圍,而不是和的實際Object
大小Map
。讓我們增大尺寸,看看它如何影響效能。
當屬性大小為1000時,Object
的插入速度比Object
快70%,迭代速度比Map
慢2倍。
我嘗試了許多不同的Object
/Object
大小和整數鍵範圍的組合,但未能想出一個明確的模式。但我看到的總體趨勢是,隨著大小的增長,使用一些相對較小的整數作為鍵,Object
在插入方面的效能可以比map
更好,總是與刪除大致相同,迭代速度是map
的4到5倍。最大整數鍵的閾值,即Object
在插入時開始變慢的閾值,將隨著Object
的大小而增長。例如,當該Object
只有100個表項時,閾值為1200;當它有10000個條目時,閾值似乎在24000左右。
最後,我們來看看最後一種鍵——數位鍵。
從技術上講,以前的整數鍵也是數位的。這裡的數位鍵特指生成的數位字串Math.random().toString()
。
結果與字串-鍵的情況類似:map
開始時比Object
快得多(插入和刪除快2倍,迭代快4-5倍),但隨著大小的增加,增量越來越小。
巢狀物件/地圖呢?
基準測試的另一個重要方面是記憶體利用率。
由於我無法控制瀏覽器環境中的垃圾收集器,因此我決定在 Node.js 中執行基準測試。
我建立了一個小指令碼來測量它們各自的記憶體使用情況,並在每次測量中手動觸發完全垃圾收集。執行它,node --expose-gc
我得到以下結果:
{
object: {
'string-key': {
'10000': 3.390625,
'50000': 19.765625,
'100000': 16.265625,
'500000': 71.265625,
'1000000': 142.015625
},
'numeric-key': {
'10000': 1.65625,
'50000': 8.265625,
'100000': 16.765625,
'500000': 72.265625,
'1000000': 143.515625
},
'integer-key': {
'10000': 0.25,
'50000': 2.828125,
'100000': 4.90625,
'500000': 25.734375,
'1000000': 59.203125
}
},
map: {
'string-key': {
'10000': 1.703125,
'50000': 6.765625,
'100000': 14.015625,
'500000': 61.765625,
'1000000': 122.015625
},
'numeric-key': {
'10000': 0.703125,
'50000': 3.765625,
'100000': 7.265625,
'500000': 33.265625,
'1000000': 67.015625
},
'integer-key': {
'10000': 0.484375,
'50000': 1.890625,
'100000': 3.765625,
'500000': 22.515625,
'1000000': 43.515625
}
}
}
很明顯,Map
消耗的記憶體比Object
少20%到50%,這並不奇怪,因為Map
它不儲存屬性描述符,例如writable
// like 。enumerable``configurable``Object
那麼我們能從這一切中得到什麼?
Map
比Object
更快,除非您有小的整數和陣列索引鍵,而且它的記憶體效率更高。Map
; 如果你想要一個固定的鍵值集合(例如記錄),請使用Object
,並注意原型繼承帶來的陷阱。如果您確切瞭解 V8 如何優化的細節,
Map
或者只是想指出我的基準測試中的缺陷,請聯絡我。我很樂意根據您的資訊更新這篇文章!
Map
是 ES6 的一個特性。到目前為止,我們大多數人都不應該擔心它的相容性,除非你的目標使用者群是一些小眾的舊瀏覽器。“舊”是指比 IE 11 更早,因為即使 IE 11 也支援Map而此時 IE 11已死。我們不應該在預設情況下盲目地轉譯和新增 polyfill 到目標 ES5,因為它不僅會膨脹你的包大小,而且與現代 JavaScript 相比執行起來很慢。最重要的是,它會懲罰 99.999% 的使用現代瀏覽器的使用者。
另外,我們不必放棄對舊版瀏覽器的支援——nomodule
通過提供後備包來提供舊版程式碼,這樣我們就可以避免使用現代瀏覽器降低存取者的體驗。
JavaScript 語言在不斷髮展,平臺在優化現代 JavaScript 方面也越來越好。我們不應該以瀏覽器相容性為藉口忽略所有已做出的改進。
原文翻譯於https://www.zhenghao.io/posts/object-vs-map
以上就是JavaScript中Map與Object應用場景的詳細內容,更多關於JavaScript中Map Object的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45