成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

在 JavaScript 中,什么時候使用 Map 或勝過 Object

開發(fā) 前端
在 Hash Map 中使用對象最明顯的缺點是,對象只允許鍵是字符串和 symbol。任何其他類型的鍵都會通過 toString 方法被隱含地轉換為字符串。

在 JavaScript 中,對象是很方便的。它們允許我們輕松地將多個數(shù)據(jù)塊組合在一起。在ES6之后,又出了一個新的語言補充-- Map。在很多方面,它看起來像是一個功能更強的對象,但接口卻有些笨拙。

然而,大多數(shù)開發(fā)者在需要 hash map 的時候還是會使用對象,只有當他們意識到鍵值不能只是字符串的時候才會轉而使用 Map。因此,Map 在當今的 JavaScript 社區(qū)中仍然沒有得到充分的使用。

在本文本中,我會列舉一些應該更多考慮使用 Map 的一些原因。

為什么對象不符合 Hash Map 的使用情況

在 Hash Map 中使用對象最明顯的缺點是,對象只允許鍵是字符串和 symbol。任何其他類型的鍵都會通過 ??toString?? 方法被隱含地轉換為字符串。

const foo = []
const bar = {}
const obj = {[foo]: 'foo', [bar]: 'bar'}
console.log(obj) // {"": 'foo', [object Object]: 'bar'}

更重要的是,使用對象做 Hash Map 會造成混亂和安全隱患。

不必要的繼承

在ES6之前,獲得 hash map 的唯一方法是創(chuàng)建一個空對象:

const hashMap = {}

然而,在創(chuàng)建時,這個對象不再是空的。盡管 hashMap 是用一個空的對象字面量創(chuàng)建的,但它自動繼承了 Object.prototype。這就是為什么我們可以在 hashMap 上調(diào)用hasOwnProperty、toString、constructor 等方法,盡管我們從未在該對象上明確定義這些方法。

由于原型繼承,我們現(xiàn)在有兩種類型的屬性被混淆了:存在于對象本身的屬性,即它自己的屬性,以及存在于原型鏈的屬性,即繼承的屬性。

因此,我們需要一個額外的檢查(例如hasOwnProperty)來確保一個給定的屬性確實是用戶提供的,而不是從原型繼承的。

除此之外,由于屬性解析機制在 JavaScrip t中的工作方式,在運行時對 Object.prototype 的任何改變都會在所有對象中引起連鎖反應。這就為原型污染攻擊打開了大門,這對大型的JavaScript 應用程序來說是一個嚴重的安全問題。

不過,我們可以通過使用 Object.create(null) 來解決這個問題,它可以生成一個不繼承Object.prototype的對象。

名稱沖突

當一個對象自己的屬性與它的原型上的屬性有名稱沖突時,它就會打破預期,從而使程序崩潰。

例如,我們有一個函數(shù) foo,它接受一個對象。

function foo(obj) {
//...
for (const key in obj) {
if (obj.hasOwnProperty(key)) {

}
}
}

obj.hasOwnProperty(key)有一個可靠性風險:考慮到屬性解析機制在JavaScript中的工作方式,如果 obj 包含一個開發(fā)者提供的具有相同名稱的 hasOwnProperty 屬性,那就會對Object.prototype.hasOwnProperty產(chǎn)生影響。因此,我們不知道哪個方法會在運行時被準確調(diào)用。

可以做一些防御性編程來防止這種情況。例如,我們可以從 Object.prototype 中 "借用""真正的 hasOwnProperty 來代替:

function foo(obj) {
//...
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
// ...
}
}
}

還有一個更簡短的方法就是在一個對象的字面量上調(diào)用該方法,如{}.hasOwnProperty.call(key),不過這也挺麻煩的。這就是為什么還會新出一個靜態(tài)方法Object.hasOwn 的原因了。

次優(yōu)的人機工程學

Object 沒有提供足夠的人機工程學,不能作為 hash map 使用,許多常見的任務不能直觀地執(zhí)行。

size

Object 并沒有提供方便的API來獲取 size,即屬性的數(shù)量。而且,對于什么是一個對象的 size ,還有一些細微的差別:

  • 如果只關心字符串、可枚舉的鍵,那么可以用Object.keys() 將鍵轉換為數(shù)組,并獲得其length。
  • 如果k只想要不可枚舉的字符串鍵,那么必須得使用Object.getOwnPropertyNames 來獲得一個鍵的列表并獲得其 length。
  • 如果只對 symbol  鍵感興趣,可以使用getOwnPropertySymbols? 來顯示 symbol  鍵。或者可以使用Reflect.ownKeys 來一次獲得字符串鍵和 symbol  鍵,不管它是否是可枚舉的。

上述所有選項的運行時復雜度為**O(n)**,因為我們必須先構造一個鍵的數(shù)組,然后才能得到其長度。

iterate

循環(huán)遍歷對象也有類似的復雜性。

我們可以使用 for...in循環(huán)。但它會讀取到繼承的可枚舉屬性。

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.entry 來獲得一個可枚舉的字符串鍵(或/和值)的列表,并通過該列表進行迭代,這引入了一個額外的開銷步驟。

還有一個是 插入對象的鍵的順序并不是按我們的順序來的,這是一個很蛋疼的地方。在大多數(shù)瀏覽器中,整數(shù)鍵是按升序排序的,并優(yōu)先于字符串鍵,即使字符串鍵是在整數(shù)鍵之前插入的:

const obj = {}
obj.foo = 'first'
obj[2] = 'second'
obj[1] = 'last'
console.log(obj) // {1: 'last', 2: 'second', foo: 'first'}

clear

沒有簡單的方法來刪除一個對象的所有屬性,我們必須用 delete 操作符一個一個地刪除每個屬性,這在歷史上是眾所周知的慢。

檢查屬性是否存在

最后,我們不能依靠點/括號符號來檢查一個屬性的存在,因為值本身可能被設置為 undefined。相反,得使用 Object.prototype.hasOwnProperty 或 Object.hasOwn。

const obj = {a: undefined}
Object.hasOwn(obj, 'a') // true

Map

ES6 為我們帶來了 Map,首先,與只允許鍵值為字符串和 symbols 的 Object 不同,Map 支持任何數(shù)據(jù)類型的鍵。

但更重要的是,Map 在用戶定義的和內(nèi)置的程序數(shù)據(jù)之間提供了一個干凈的分離,代價是需要一個額外的 Map.prototype.get 來獲取對應的項。

Map 也提供了更好的人機工程學。Map 默認是一個可迭代的對象。這說明可以用 for ... of 輕松地迭代一個 Map,并做一些事情,比如使用嵌套的解構來從 Map 中取出第一個項。

const [[firstKey, firstValue]] = map

與 Object 相比,Map 為各種常見任務提供了專門的API:

  • Map.prototype.has? 檢查一個給定的項是否存在,與必須在對象上使用Object.prototype.hasOwnProperty/Object.hasOwn 相比,不那么尷尬了。
  • Map.prototype.get 返回與提供的鍵相關的值。有的可能會覺得這比對象上的點符號或括號符號更笨重。不過,它提供了一個干凈的用戶數(shù)據(jù)和內(nèi)置方法之間的分離。
  • Map.prototype.size 返回 Map 中的項的個數(shù),與獲取對象大小的操作相比,這明顯好太多了。此外,它的速度也更快。
  • Map.prototype.clear 可以刪除 Map 中的所有項,它比 delete 操作符快得多。

性能差異

在 JavaScript 社區(qū)中,似乎有一個共同的信念,即在大多數(shù)情況下,Map 要比 Object 快。有些人聲稱通過從 Object 切換到 Map 可以看到明顯的性能提升。

我在 LeetCode 上也證實了這種想法,對于數(shù)據(jù)量大的 Object 會超時,但 Map 上則不會。

然而,說 "Map 比 Object 快" 可能是算一種歸納性的,這兩者一定有一些細微的差別,我們可以通過一些例子,把它找出來。

測試

測試用例有一個表格,主要測試 Object 和 Map 在插入、迭代和刪除數(shù)據(jù)的速度。

插入和迭代的性能是以每秒的操作來衡量的。這里使用了一個實用函數(shù) measureFor,它重復運行目標函數(shù),直到達到指定的最小時間閾值(即用戶界面上的 duration 輸入字段)。它返回這樣一個函數(shù)每秒鐘被執(zhí)行的平均次數(shù)。

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 使用 Map.prototype.delete 的時間進行比較。也可以使用Map.prototype.clear,但這有悖于基準測試的目的,因為我知道它肯定會快得多。

在這三種操作中,我更關注插入操作,因為它往往是我在日常工作中最常執(zhí)行的操作。對于迭代性能,很難有一個全面的基準,因為我們可以對一個給定的對象執(zhí)行許多不同的迭代變體。這里我只測量 for ... in 循環(huán)。

在這里使用了三種類型的 key。

  • 字符串,例如:Yekwl7caqejth7aawelo4。
  • 整數(shù)字符串,例如:123。
  • 由Math.random().toString() 生成的數(shù)字字符串,例如:0.4024025689756525。

所有的鍵都是隨機生成的,所以我們不會碰到V8實現(xiàn)的內(nèi)聯(lián)緩存。我還在將整數(shù)和數(shù)字鍵添加到對象之前,使用 toString 明確地將其轉換為字符串,以避免隱式轉換的開銷。

最后,在基準測試開始之前,還有一個至少100ms的熱身階段,在這個階段,我們反復創(chuàng)建新的對象和 Map,并立即丟棄。

如果你也想玩,代碼已經(jīng)放在 CodeSandbox 上。

我從大小為 100 個屬性/項的 Object 和 Map 開始,一直到 5000000,并讓每種類型的操作持續(xù)運行 10000ms,看看它們之間的表現(xiàn)如何。下面是測試結果:

string keys

一般來說,當鍵為(非數(shù)字)字符串時,Map 在所有操作上都優(yōu)于 Object。

圖片

但細微之處在于,當數(shù)量并不真正多時(低于100000),Map 在插入速度上 是Object 的兩倍,但當規(guī)模超過 100000 時,性能差距開始縮小。

圖片

上圖顯示了隨著條目數(shù)的增加(x軸),插入率如何下降(y軸)。然而,由于X軸擴展得太寬(從100 到 1000000),很難分辨這兩條線之間的差距。

然后用對數(shù)比例來處理數(shù)據(jù),做出了下面的圖表。

圖片

可以清楚地看出這兩條線正在重合。

這里又做了一張圖,畫出了在插入速度上 Map 比 Object 快多少。你可以看到 Map 開始時比 Object 快 2 倍左右。然后隨著時間的推移,性能差距開始縮小。最終,當大小增長到 5000000時,Map 只快了 30%。

圖片

雖然我們中的大多數(shù)人永遠不會在一個 Object 或 Map 中擁有超過1 00 萬的條數(shù)據(jù)。對于幾百或幾千個數(shù)據(jù)的規(guī)模,Map 的性能至少是 Object 的兩倍。因此,我們是否應該就此打住,并開始重構我們的代碼庫,全部采用 Map?

這不太靠譜......或者至少不能期望我們的應用程序變得快 2 倍。記住我們還沒有探索其他類型的鍵。下面我們看一下整數(shù)鍵。

integer keys

我之所以特別想在有整數(shù)鍵的對象上運行基準,是因為V8在內(nèi)部優(yōu)化了整數(shù)索引的屬性,并將它們存儲在一個單獨的數(shù)組中,可以線性和連續(xù)地訪問。但我找不到任何資源來證實它對 Map 也采用了同樣的優(yōu)化方式。

我們首先嘗試在 [0, 1000] 范圍內(nèi)的整數(shù)鍵。

圖片

如我所料,Object 這次的表現(xiàn)超過了 Map。它們的插入速度比 Map 快65%,迭代速度快16%。

接著, 擴大范圍,使鍵中的最大整數(shù)為 1200。

圖片

似乎現(xiàn)在 Map 的插入速度開始比 Object 快一點,迭代速度快 5 倍。

現(xiàn)在,我們只增加了整數(shù)鍵的范圍,而不是 Object 和 Map 的實際大小。讓我們加大 size,看看這對性能有什么影響。

圖片

當屬性 size 為 1000 時,Object 最終比 Map 的插入速度快 70%,迭代速度慢2倍。

我玩了一堆 Object/Map size 和整數(shù)鍵范圍的不同組合,但沒有想出一個明確的模式。但我看到的總體趨勢是,隨著 size 的增長,以一些相對較小的整數(shù)作為鍵值,Object 在插入方面比Map 更有性能,在刪除方面總是大致相同,迭代速度慢4或5倍。

Object 在插入時開始變慢的最大整數(shù)鍵的閾值會隨著 Object 的大小而增長。例如,當對象只有100個條數(shù)據(jù),閾值是1200;當它有 10000 個條目時,閾值似乎是 24000 左右。

numeric keys

最后,讓我們來看看最后一種類型的按鍵--數(shù)字鍵。

從技術上講,之前的整數(shù)鍵也是數(shù)字鍵。這里的數(shù)字鍵特指由 Math.random().toString() 生成的數(shù)字字符串。

結果與那些字符串鍵的情況類似。Map 開始時比 Object 快得多(插入和刪除快2倍,迭代快4-5倍),但隨著我們規(guī)模的增加,差距也越來越小。

內(nèi)存使用情況

基準測試的另一個重要方面是內(nèi)存利用率。

由于我無法控制瀏覽器環(huán)境中的垃圾收集器,這里決定在 Node 中運行基準測試。

這里創(chuàng)建了一個小腳本來測量它們各自的內(nèi)存使用情況,并在每次測量中手動觸發(fā)了完全的垃圾收集。用 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 消耗的內(nèi)存少20%到50%,這并不奇怪,因為 Map 不像 Object 那樣存儲屬性描述符,比如 writable/enumerable/configurable 。

總結

那么,我們能從這一切中得到什么呢?

  • Map 比 Object 快,除非有小的整數(shù)、數(shù)組索引的鍵,而且它更節(jié)省內(nèi)存。
  • 如果你需要一個頻繁更新的 hash map,請使用 Map;如果你想一個固定的鍵值集合(即記錄),請使用Object,并注意原型繼承帶來的陷阱。
責任編輯:姜華 來源: 大遷世界
相關推薦

2020-07-24 09:20:44

MapObject前端

2012-09-24 10:20:39

JavaScriptJS

2009-06-19 16:29:47

EJBXML

2017-06-28 15:06:51

PythonLambda函數(shù)

2022-05-19 10:27:34

機器學習人工智能

2020-01-05 23:28:51

MQ消息進程

2017-04-05 21:43:08

MQ互聯(lián)網(wǎng)架構

2012-07-26 10:27:31

PHP

2020-05-12 11:25:50

MySQLES數(shù)據(jù)庫

2017-05-15 09:55:07

2015-07-08 15:55:01

NSStringcopystrong

2013-09-29 17:13:59

PowerShell工作流

2013-11-28 16:03:24

2025-02-28 09:04:08

2024-10-29 08:52:01

Go協(xié)作式調(diào)度

2010-11-09 13:58:03

SQL Server鎖

2024-08-05 01:22:16

2009-06-09 22:11:44

JavaScriptObject

2020-06-17 10:35:16

機器學習AI人工智能

2021-04-19 09:20:01

Go 搶占 P語言
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 91精品久久久 | 午夜一区二区三区在线观看 | a黄视频| 一区二区三区av夏目彩春 | 一级黄色录像片子 | 福利视频一区二区 | 国产在线一区二区三区 | 日韩不卡一区二区 | 毛片在线免费播放 | 亚洲精品乱码久久久久久按摩观 | 在线资源视频 | 国产精品18hdxxxⅹ在线 | 日韩在线免费看 | 不卡视频一区二区三区 | 在线观看黄色电影 | 午夜影晥| 国产情侣在线看 | 日本三级电影在线免费观看 | 观看av | 久久久久久久久久久久久9999 | 亚洲视频免费观看 | 日本午夜在线视频 | 欧美一区二区三区视频在线观看 | 农村妇女毛片精品久久久 | 成人不卡视频 | 中文字幕视频一区二区 | 久久只有精品 | 国产japanhdxxxx麻豆 | 噜噜噜噜狠狠狠7777视频 | 免费一看一级毛片 | 日本天堂一区 | 亚洲精品一级 | аⅴ资源新版在线天堂 | 91精品国产高清久久久久久久久 | 精品1区| 国产伦精品一区二区三区视频金莲 | 日韩中文一区二区 | 手机av在线| 免费高清av | 国产视频中文字幕在线观看 | 日本在线观看视频 |