Object 和 Map| ,在JS 項(xiàng)目中給你選,你會(huì)選??

在日常的 JavaScript 項(xiàng)目中,我們最常用到的數(shù)據(jù)結(jié)構(gòu)就是各種形式的鍵值對(duì)格式了(key-value pair)。在 JavaScript 中,除了最基礎(chǔ)的Object是該格式外,ES6 新增的Map也同樣是鍵值對(duì)格式。它們的用法在很多時(shí)候都十分接近。不知道有沒有人和我一樣糾結(jié)過該選擇哪個(gè)去使用呢?在本菜最近的項(xiàng)目中,我又遇到了這樣的煩惱,索性一不做二不休,去對(duì)比一下究竟該使用哪一個(gè)。

本文將會(huì)探討一下Object和Map的不同,從多個(gè)角度對(duì)比一下Object和Map:

用法的區(qū)別:在某些情況下的用法會(huì)截然不同

句法的區(qū)別:創(chuàng)建以及增刪查改的句法區(qū)別

性能的區(qū)別:速度和內(nèi)存占用情況

希望讀完本文的你可以在日后的項(xiàng)目中做出更為合適的選擇。

用法對(duì)比

對(duì)于Object而言,它鍵(key)的類型只能是字符串,數(shù)字或者Symbol;而對(duì)于Map而言,它可以是任何類型。(包括 Date,Map,或者自定義對(duì)象)

Map中的元素會(huì)保持其插入時(shí)的順序;而Object則不會(huì)完全保持插入時(shí)的順序,而是根據(jù)如下規(guī)則進(jìn)行排序:

非負(fù)整數(shù)會(huì)最先被列出,排序是從小到大的數(shù)字順序

然后所有字符串,負(fù)整數(shù),浮點(diǎn)數(shù)會(huì)被列出,順序是根據(jù)插入的順序

最后才會(huì)列出Symbol,Symbol也是根據(jù)插入的順序進(jìn)行排序的

讀取Map的長度很簡(jiǎn)單,只需要調(diào)用其.size()方法即可;而讀取Object的長度則需要額外的計(jì)算:Object.keys(obj).length

Map 是可迭代對(duì)象,所以其中的鍵值對(duì)是可以通過for of循環(huán)或.foreach()方法來迭代的;而普通的對(duì)象鍵值對(duì)則默認(rèn)是不可迭代的,只能通過for in循環(huán)來訪問(或者使用Object.keys(o)、Object.values(o)、Object.entries(o)來取得表示鍵或值的數(shù)字)迭代時(shí)的順序就是上面提到的順序。

consto = {};constm =newMap();o[Symbol.iterator] !==undefined;// falsem[Symbol.iterator] !==undefined;// true復(fù)制代碼

在Map中新增鍵時(shí),不會(huì)覆蓋其原型上的鍵;而在Object中新增鍵時(shí),則有可能覆蓋其原型上的鍵:

Object.prototype.x =1;consto = {x:2};constm =newMap([[x,2]]);o.x;// 2,x = 1 被覆蓋了m.x;// 1,x = 1 不會(huì)被覆蓋復(fù)制代碼

JSON默認(rèn)支持Object而不支持Map。若想要通過JSON傳輸Map則需要使用到.toJSON()方法,然后在JSON.parse()中傳入復(fù)原函數(shù)來將其復(fù)原。

對(duì)于JSON這里就不具體展開了,有興趣的朋友可以看一下這:JSON 的序列化和解析

consto = {x:1};constm =newMap([['x',1]]);consto2 =JSON.parse(JSON.stringify(o));// {x:1}constm2 =JSON.parse(JSON.stringify(m))// {}復(fù)制代碼

句法對(duì)比

創(chuàng)建時(shí)的區(qū)別

Obejct

consto = {};// 對(duì)象字面量consto =newObject();// 調(diào)用構(gòu)造函數(shù)consto =Object.create(null);// 調(diào)用靜態(tài)方法 Object.create 復(fù)制代碼

對(duì)于Object來說,我們?cè)?95%+ 的情況下都會(huì)選擇對(duì)象字面量,它不僅寫起來最簡(jiǎn)單,而且相較于下面的函數(shù)調(diào)用,在速度方面會(huì)更為高效。對(duì)于構(gòu)建函數(shù),可能唯一使用到的情況就是顯式的封裝一個(gè)基本類型;而Object.create可以為對(duì)象設(shè)定原型。

Map

constm =newMap();// 調(diào)用構(gòu)造函數(shù)復(fù)制代碼

和Object不同,Map沒有那么多花里胡哨的創(chuàng)建方法,通常只會(huì)使用其構(gòu)造函數(shù)來創(chuàng)建。

除了上述方法之外,我們也可以通過Function.prototype.apply()、Function.prototype.call()、reflect.apply()、Reflect.construct()方法來調(diào)用Object和Map的構(gòu)造函數(shù)或者Object.create()方法,這里就不展開了。

新增/讀取/刪除元素時(shí)的區(qū)別

Obejct

consto = {};//新增/修改o.x =1;o['y'] =2;//讀取o.x;// 1o['y'];// 2//或者使用 ES2020 新增的條件屬性訪問表達(dá)式來讀取o?.x;// 1o?.['y'];// 2//刪除deleteo.b;復(fù)制代碼

對(duì)于新增元素,看似使用第一種方法更為簡(jiǎn)單,不過它也有些許限制:

屬性名不能包含空格和標(biāo)點(diǎn)符號(hào)

屬性名不能以數(shù)字開頭

對(duì)于條件屬性訪問表達(dá)式的更多內(nèi)容可以看一下這:條件屬性訪問表達(dá)式

Map

constm =newMap();//新增/修改m.set('x',1);//讀取map.get('x');//刪除map.delete('b');復(fù)制代碼

對(duì)于簡(jiǎn)單的增刪查改來說,Map上的方法使用起來也是十分便捷的;不過在進(jìn)行聯(lián)動(dòng)操作時(shí),Map中的用法則會(huì)略顯臃腫:

constm =newMap([['x',1]]);// 若想要將 x 的值在原有基礎(chǔ)上加一,我們需要這么做:m.set('x', m.get('x') +1);m.get('x');// 2consto = {x:1};// 在對(duì)象上修改則會(huì)簡(jiǎn)單許多:o.x++;o.x// 2復(fù)制代碼

性能對(duì)比

接下來我們來討論一下Object和Map的性能。不知道各位有沒有聽說過 Map 的性能優(yōu)于 Object 的說法,我反正是見過不少次,甚至在 JS 高程四中也提到了Map對(duì)比Object時(shí)性能的優(yōu)勢(shì);不過對(duì)于性能的概括都十分的籠統(tǒng),所以我打算做一些測(cè)試來對(duì)比一下它們的區(qū)別。

測(cè)試方法

在這里我進(jìn)行的對(duì)于性能測(cè)試的都是基于v8 引擎的。速度會(huì)通過 JS 標(biāo)準(zhǔn)庫自帶的performance.now()函數(shù)來判斷,內(nèi)存使用情況會(huì)通過Chrome devtool中的memory來查看。

對(duì)于速度測(cè)試,因?yàn)閱我坏牟僮魉俣忍炝耍芏鄷r(shí)候performance.now()會(huì)返回 0。所以我進(jìn)行了 10000 次的循環(huán)然后判斷時(shí)間差。因?yàn)檠h(huán)本身也會(huì)占據(jù)一部分時(shí)間,所以以下的測(cè)試只能作為一個(gè)大致的參考。

創(chuàng)建時(shí)的性能

測(cè)試用的代碼如下

letn,? n2 =5;// 速度while(n2--) {letp1 = performance.now();? n =10000;while(n--) {leto = {}; }letp2 = performance.now();? n =10000;while(n--) {letm =newMap(); }letp3 = performance.now();console.log(`Object:${(p2 - p1).toFixed(3)}ms, Map:${(p3 - p2).toFixed(3)}ms`);}// 內(nèi)存classTest{}lettest =newTest();test.o = o;test.m = m;復(fù)制代碼

首先進(jìn)行對(duì)比的是創(chuàng)建Object和Map時(shí)的表現(xiàn)。對(duì)于創(chuàng)建的速度表現(xiàn)如下:

我們可以發(fā)現(xiàn)創(chuàng)建Object的速度會(huì)快于Map。對(duì)于內(nèi)存使用情況則如下:

我們主要關(guān)注其Retained Size,它表示了為其分配的空間。(即刪除時(shí)釋放的內(nèi)存大小)

通過對(duì)比我們可以發(fā)現(xiàn),空的Object會(huì)比空的Map占用更少的內(nèi)。所以這一輪Object贏得一籌。

新增元素時(shí)的性能

測(cè)試用的代碼如下

console.clear();letn,? n2 =5;leto = {}, m =newMap();// 速度while(n2--) {letp1 = performance.now();? n =10000;while(n--) { o[Math.random()] =Math.random(); }letp2 = performance.now();? n =10000;while(n--) { m.set(Math.random(),Math.random()); }letp3 = performance.now();console.log(`Object:${(p2 - p1).toFixed(3)}ms, Map:${(p3 - p2).toFixed(3)}ms`);}// 內(nèi)存classTest{}lettest =newTest();test.o = o;test.m = m;復(fù)制代碼

對(duì)于新建元素時(shí)的速度表現(xiàn)如下:

我們可以發(fā)現(xiàn)新建元素時(shí),Map的速度會(huì)快于Object。對(duì)于內(nèi)存使用情況則如下:

通過對(duì)比我們可以發(fā)現(xiàn),在擁有一定數(shù)量的元素時(shí),Object會(huì)比Map占用多了約 78% 的內(nèi)存。我也進(jìn)行了多次的測(cè)試,發(fā)現(xiàn)在擁有足夠的元素時(shí),這個(gè)百分比是十分穩(wěn)定的。所以說,在需要進(jìn)行很多新增操作,且需要儲(chǔ)存許多數(shù)據(jù)的時(shí)候,使用Map會(huì)更高效。

讀取元素時(shí)的性能

測(cè)試用的代碼如下

letn;leto = {}, m =newMap();n =10000;while(n--) { o[Math.random()] =Math.random(); }n =10000;while(n--) { m.set(Math.random(),Math.random()); }letp1 = performance.now();for(keyino) {letk = o[key]; }letp2 = performance.now();for([key]ofm) {letk = m.get(key); }letp3 = performance.now();`Object:${(p2 - p1).toFixed(3)}ms, Map:${(p3 - p2).toFixed(3)}ms`復(fù)制代碼

對(duì)于讀取元素時(shí)的速度表現(xiàn)如下:

通過對(duì)比,我們可以發(fā)現(xiàn)Object略占優(yōu)勢(shì),但總體差別不大。

刪除元素時(shí)的性能

不知道大家是否聽說過delete操作符性能低下,甚至有很多時(shí)候?yàn)榱诵阅?,?huì)寧可將值設(shè)置為undefined而不使用delete操作符的說法。但其實(shí)在v8近來的優(yōu)化下,它的效率已經(jīng)提升許多了。

測(cè)試用的代碼如下

letn;leto = {}, m =newMap();n =10000;while(n--) { o[Math.random()] =Math.random(); }n =10000;while(n--) { m.set(Math.random(),Math.random()); }letp1 = performance.now();for(keyino) {deleteo[key]; }letp2 = performance.now();for([key]ofm) { m.delete(key); }letp3 = performance.now();`Object:${(p2 - p1).toFixed(3)}ms, Map:${(p3 - p2).toFixed(3)}ms`復(fù)制代碼

對(duì)于刪除元素時(shí)的速度表現(xiàn)如下:

我們可以發(fā)現(xiàn)在進(jìn)行刪除操作時(shí),Object的速度會(huì)略占優(yōu),但整體差別其實(shí)也并不大。

特殊情況

其實(shí)除了最基本的情況之外,還有一種特殊的情況。還記得我們?cè)谇懊嫣岬降腛bject中鍵的排序嗎?我們提到了其中的非負(fù)整數(shù)會(huì)被最先列出。其實(shí)對(duì)于非負(fù)整數(shù)作為鍵的值和其余類型作為鍵的值來說,v8是會(huì)對(duì)它們進(jìn)行區(qū)別對(duì)待的。負(fù)整數(shù)作為鍵的部分會(huì)被當(dāng)成數(shù)組對(duì)待,即非負(fù)整數(shù)具有一定的連續(xù)性時(shí),會(huì)被當(dāng)成快數(shù)組,而過于稀疏時(shí)會(huì)被當(dāng)成慢數(shù)組。

對(duì)于快數(shù)組,它擁有連續(xù)的內(nèi)存,所以在進(jìn)行讀寫時(shí)會(huì)更快,且占用更少的內(nèi)存。更多的內(nèi)容可以看一下這:探究JS V8引擎下的“數(shù)組”底層實(shí)現(xiàn)

在鍵為連續(xù)非負(fù)整數(shù)時(shí),性能如下:

我們可以看到Object不僅平均速度更快了,其占用的內(nèi)存也大大減少了。

總結(jié)

通過對(duì)比我們可以發(fā)現(xiàn),Map和Object各有千秋,對(duì)于不同的情況下,我們應(yīng)當(dāng)作出不同的選擇。所以我總結(jié)了一下我認(rèn)為使用Map和Object更為合適的時(shí)機(jī)。

使用Map:

儲(chǔ)存的鍵不是字符串/數(shù)字/或者Symbol時(shí),選擇Map,因?yàn)镺bject并不支持

儲(chǔ)存大量的數(shù)據(jù)時(shí),選擇Map,因?yàn)樗加玫膬?nèi)存更小

需要進(jìn)行許多新增/刪除元素的操作時(shí),選擇Map,因?yàn)樗俣雀?/p>

需要保持插入時(shí)的順序的話,選擇Map,因?yàn)镺bject會(huì)改變排序

需要迭代/遍歷的話,選擇Map,因?yàn)樗J(rèn)是可迭代對(duì)象,迭代更為便捷

使用Object:

只是簡(jiǎn)單的數(shù)據(jù)結(jié)構(gòu)時(shí),選擇Object,因?yàn)樗跀?shù)據(jù)少的時(shí)候占用內(nèi)存更少,且新建時(shí)更為高效

需要用到JSON進(jìn)行文件傳輸時(shí),選擇Object,因?yàn)镴SON不默認(rèn)支持Map

需要對(duì)多個(gè)鍵值進(jìn)行運(yùn)算時(shí),選擇Object,因?yàn)榫浞ǜ鼮楹?jiǎn)潔

需要覆蓋原型上的鍵時(shí),選擇Object

雖然Map在很多情況下會(huì)比Object更為高效,不過Object永遠(yuǎn)是JS中最基本的引用類型,它的作用也不僅僅是為了儲(chǔ)存鍵值對(duì)。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容