利用JavaScript的Map提升性能的方法詳解
前言
在ES6中引入JavaScript的新特性中,我們看到了Set和Map的介紹。與常規(guī)對(duì)象和Array不同的是,它們是“鍵控集合(keyed collections)”。這就是說(shuō)它們的行為有稍許不同,并且在特定的上下文中使用,它們可以提供相當(dāng)大的性能優(yōu)勢(shì)。
在這篇文章中,我將剖析Map,它究竟有何不同,哪里可以派上用場(chǎng),相比于常規(guī)對(duì)象有什么性能優(yōu)勢(shì)。
Map與常規(guī)對(duì)象有什么不同
Map和常規(guī)對(duì)象主要有2個(gè)不同之處。
1.無(wú)限制的鍵(Key)
常規(guī)JavaScript對(duì)象的鍵必須是String或Symbol,下面的對(duì)象說(shuō)明的這一點(diǎn):
const symbol = Symbol(); const string2 = 'string2'; const regularObject = { string1: 'value1', [string2]: 'value2', [symbol]: 'value3' };
相比之下,Map允許你使用函數(shù)、對(duì)象和其它簡(jiǎn)單的類型(包括NaN)作為鍵,如下代碼:
const func = () => null; const object = {}; const array = []; const bool = false; const map = new Map(); map.set(func, 'value1'); map.set(object, 'value2'); map.set(array, 'value3'); map.set(bool, 'value4'); map.set(NaN, 'value5');
在鏈接不同數(shù)據(jù)類型時(shí),這個(gè)特性提供了極大的靈活性。
2.直接遍歷
在常規(guī)對(duì)象中,為了遍歷keys、values和entries,你必須將它們轉(zhuǎn)換為數(shù)組,如使用Object.keys()、Object.values()和Object.entries(),或者使用for ... in循環(huán),因?yàn)槌R?guī)對(duì)象不能直接遍歷,另外for ... in循環(huán)還有一些限制:它僅僅遍歷可枚舉屬性、非Symbol屬性,并且遍歷的順序是任意的。
而Map可以直接遍歷,并且由于它是鍵控集合,遍歷的順序和插入鍵值的順序是一致的。你可以使用for ... of循環(huán)或forEach方法來(lái)遍歷Map的entries,如下代碼:
for (let [key, value] of map) { console.log(key); console.log(value); }; map.forEach((key, value) => { console.log(key); console.log(value); });
還有一個(gè)好處就是,你可以調(diào)用map.size屬性來(lái)獲取鍵值數(shù)量,而對(duì)于常規(guī)對(duì)象,為了做到這樣你必須先轉(zhuǎn)換為數(shù)組,然后獲取數(shù)組長(zhǎng)度,如:Object.keys({}).length。
Map和Set有何不同
Map的行為和Set非常相似,并且它們都包含一些相同的方法,包括:has、get、set、delete。它們兩者都是鍵控集合,就是說(shuō)你可以使用像forEach的方法來(lái)遍歷元素,順序是按照插入鍵值排列的。
最大的不同是Map通過(guò)鍵值(key/value)成對(duì)出現(xiàn),就像你可以把一個(gè)數(shù)組轉(zhuǎn)換為Set,你也可以把二維數(shù)組轉(zhuǎn)換為Map:
const set = new Set([1, 2, 3, 4]); const map = new Map([['one', 1], ['two', 2], ['three', 3], ['four', 4]]);
類型轉(zhuǎn)換
要將Map切換回?cái)?shù)組,你可以使用ES6的結(jié)構(gòu)語(yǔ)法:
const map = new Map([['one', 1], ['two', 2]]); const arr = [...map];
到目前為止,將Map與常規(guī)對(duì)象的互相轉(zhuǎn)換依然不是很方便,所以你可能需要依賴一個(gè)函數(shù)方法,如下:
const mapToObj = map => { const obj = {}; map.forEach((key, value) => { obj[key] = value }); return obj; }; const objToMap = obj => { const map = new Map(); Object.keys(obj).forEach(key => { map.set(key, obj[key]) }); return map; };
但是現(xiàn)在,在八月份ES2019的首次展示中,我們看見(jiàn)了Object引入了2個(gè)新方法:Object.entries()和Object.fromEntries(),這可以使上述方法簡(jiǎn)化許多:
const obj2 = Object.fromEntries(map); const map2 = new Map(Object.entries(obj));
在你使用Object.fromEntries轉(zhuǎn)換map為object之前,確保map的key在轉(zhuǎn)換為字符串時(shí)會(huì)產(chǎn)生唯一的結(jié)果,否則你將面臨數(shù)據(jù)丟失的風(fēng)險(xiǎn)。
性能測(cè)試
為了準(zhǔn)備測(cè)試,我會(huì)創(chuàng)建一個(gè)對(duì)象和一個(gè)map,它們都有1000000個(gè)相同的鍵值。
let obj = {}, map = new Map(), n = 1000000; for (let i = 0; i < n; i++) { obj[i] = i; map.set(i, i); }
然后我使用console.time()來(lái)衡量測(cè)試,由于我特定的系統(tǒng)和Node.js版本的原因,時(shí)間精度可能會(huì)有波動(dòng)。測(cè)試結(jié)果展示了使用Map的性能收益,尤其是添加和刪除鍵值的時(shí)。
查詢
let result; console.time('Object'); result = obj.hasOwnProperty('999999'); console.timeEnd('Object'); // Object: 0.250ms console.time('Map'); result = map.has(999999); console.timeEnd('Map'); // Map: 0.095ms (2.6 times faster)
添加
console.time('Object'); obj[n] = n; console.timeEnd('Object'); // Object: 0.229ms console.time('Map'); map.set(n, n); console.timeEnd('Map'); // Map: 0.005ms (45.8 times faster!)
刪除
console.time('Object'); delete obj[n]; console.timeEnd('Object'); // Object: 0.376ms console.time('Map'); map.delete(n); console.timeEnd('Map'); // Map: 0.012ms (31 times faster!)
Map在什么情況下更慢
在測(cè)試中,我發(fā)現(xiàn)一種情況常規(guī)對(duì)象的性能更好:使用for循環(huán)去創(chuàng)建常規(guī)對(duì)象和map。這個(gè)結(jié)果著實(shí)令人震驚,但是沒(méi)有for循環(huán),map添加屬性的性能勝過(guò)常規(guī)對(duì)象。
console.time('Object'); for (let i = 0; i < n; i++) { obj[i] = i; } console.timeEnd('Object'); // Object: 32.143ms let obj = {}, map = new Map(), n = 1000000; console.time('Map'); for (let i = 0; i < n; i++) { map.set(i, i); } console.timeEnd('Map'); // Map: 163.828ms (5 times slower)
舉個(gè)例子
最后,讓我們看一個(gè)Map比常規(guī)對(duì)象更合適的例子,比如說(shuō)我們想寫一個(gè)函數(shù)去檢查2個(gè)字符串是否由相同的字符串隨機(jī)排序。
console.log(isAnagram('anagram', 'gramana')); // Should return true console.log(isAnagram('anagram', 'margnna')); // Should return false
有許多方法可以做到,但是這里,map可以幫忙我們創(chuàng)建一個(gè)最簡(jiǎn)單、最快速的解決方案:
const isAnagram = (str1, str2) => { if (str1.length !== str2.length) { return false; } const map = new Map(); for (let char of str1) { const count = map.has(char) ? map.get(char) + 1 : 1; map.set(char, count); } for (let char of str2) { if (!map.has(char)) { return false; } const count = map.get(char) - 1; if (count === 0) { map.delete(char); continue; } map.set(char, count); } return map.size === 0; };
在這個(gè)例子中,當(dāng)涉及到動(dòng)態(tài)添加和刪除鍵值,無(wú)法提前確認(rèn)數(shù)據(jù)結(jié)構(gòu)(或者說(shuō)鍵值的數(shù)量)時(shí),map比object更合適。
我希望這篇文章對(duì)你有所幫助,如果你之前沒(méi)有使用過(guò)Map,不妨開(kāi)闊你的眼界,衡量現(xiàn)代JavaScript的價(jià)值體現(xiàn)。
譯者注:我個(gè)人不太同意作者的觀點(diǎn),從以上的描述來(lái)看,Map更像是以空間為代價(jià),換取速度上的提升。那么對(duì)于空間和速度的衡量,必然存在一個(gè)閾值。在數(shù)據(jù)量比較少時(shí),相比與速度的提升,其犧牲的空間代價(jià)更大,此時(shí)顯然是不適合使用Map;當(dāng)數(shù)據(jù)量足夠大時(shí),此時(shí)空間的代價(jià)影響更小。所以,看開(kāi)發(fā)者如何衡量?jī)烧咧g的關(guān)系,選擇最優(yōu)解。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
JavaScript開(kāi)發(fā)簡(jiǎn)單易懂的Svelte實(shí)現(xiàn)原理詳解
這篇文章主要為大家介紹了JavaScript開(kāi)發(fā)簡(jiǎn)單易懂的Svelte實(shí)現(xiàn)原理的內(nèi)容詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-11-11JS Pro-深入面向?qū)ο蟮某绦蛟O(shè)計(jì)之繼承的詳解
一般的面向?qū)ο蟪绦蛘Z(yǔ)言,有兩種繼承方法——接口繼承(interface inheritance)和實(shí)現(xiàn)繼承(implementation inheritance)。接口繼承只繼承方法簽名,而實(shí)現(xiàn)繼承則繼承實(shí)際的方法。在JavaScript中,函數(shù)沒(méi)有簽名,所以在JavaScript只支持實(shí)現(xiàn)繼承,而且主要是依靠原型鏈(prototype chaining)來(lái)是實(shí)現(xiàn)的2013-05-05bootstrap table方法之expandRow-collapseRow展開(kāi)或關(guān)閉當(dāng)前行數(shù)據(jù)
這篇文章主要為大家詳細(xì)介紹了bootstrap table方法之expandRow-collapseRow展開(kāi)或關(guān)閉當(dāng)前行數(shù)據(jù),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-09-09js簡(jiǎn)單實(shí)現(xiàn)用戶注冊(cè)信息的校驗(yàn)代碼
這篇文章介紹了js簡(jiǎn)單實(shí)現(xiàn)用戶注冊(cè)信息的校驗(yàn)代碼,有需要的朋友可以參考一下2013-11-11js實(shí)現(xiàn)網(wǎng)頁(yè)圖片輪換播放
這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)網(wǎng)頁(yè)圖片輪換播放,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09深入理解事件冒泡(Bubble)和事件捕捉(capture)
下面小編就為大家?guī)?lái)一篇深入理解事件冒泡(Bubble)和事件捕捉(capture)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-05-05