「 Map最佳實(shí)踐」什么時(shí)候適合使用 Map 而不是 Object

image

首先我們先有請(qǐng)Map簡(jiǎn)單介紹下自己

image

image

Map映射是一種經(jīng)典的數(shù)據(jù)結(jié)構(gòu)類型,其中數(shù)據(jù)以 key/value 的鍵值對(duì)形式存在

Map Object
默認(rèn)值 默認(rèn)不包含任何值,只包含顯式插入的鍵 一個(gè) Object 有一個(gè)原型,原型上的鍵名有可能和自己對(duì)象上設(shè)置的鍵名沖突
類型 任意 StringSymbol
長(zhǎng)度 鍵值對(duì)個(gè)數(shù)通過(guò) size 屬性獲取 鍵值對(duì)個(gè)數(shù)只能手動(dòng)計(jì)算
性能 頻繁增刪鍵值對(duì)的場(chǎng)景下表現(xiàn)更好 頻繁添加和刪除鍵值對(duì)的場(chǎng)景下未作出優(yōu)化

Map 基本用法

接受任何類型的鍵 劃重點(diǎn),是任何 any!!!

image
const testMap = new Map()

let str = '今天不學(xué)習(xí)',
    num = 666,
    keyFunction = function () {},
    keySymbol = Symbol('Web'),
    keyNull = null,
    keyUndefined = undefined,
    keyNaN = NaN
//添加鍵值對(duì)
//基本用法
testMap.set('key', 'value') // Map(1) {"key" => "value"}

testMap.set(str, '明天變辣雞')
testMap.set(num, '前端Sneaker')
testMap.set(keyFunction, '你的函數(shù)寫的好棒棒哦')
testMap.set(keySymbol, '大前端')
testMap.set(keyNull, '我是個(gè)Null')
testMap.set(keyUndefined, '我是個(gè)Undifined')
testMap.set(keyNaN, '我是個(gè)NaN')

testMap.get(function () {}) //undefined
testMap.get(Symbol('Web')) //undefined

//雖然NaN !== NaN 但是作為Map鍵名并無(wú)區(qū)別
testMap.get(NaN) //"我是個(gè)NaN"
testMap.get(Number('NaN')) //"我是個(gè)NaN"

除了NaN比較特殊外,其他Mapget方法都是通過(guò)對(duì)比鍵名是否相等(===)來(lái)獲取,不相等則返回undefined

比較 Map 和 Object

定義

//Map
const map = new Map();
map.set('key', 'value'); // Map(1) {"key" => "value"}
map.get('key'); // 'value'

//Object
const someObject = {};
someObject.key = 'value';
someObject.key; // 'value'

這里可以明顯看出其實(shí)其定義行為是十分相似的,想必看到這里大家還沒(méi)看出來(lái)Map到底在何時(shí)使用才是最佳實(shí)踐,別急接著來(lái)。

鍵名類型

JavaScript Object只接收兩種類型的鍵名 StringSymbol,你可以使用其他類型的鍵名,但是最終 JavaScript 都會(huì)隱式轉(zhuǎn)換為字符串

const obj = {}
//直接看幾種比較特殊的鍵名
obj[true] = 'Boolean'
obj[1] = 'Number'
obj[{'前端':'Sneaker'}] = '666'

Object.keys(obj) // ["1", "true", "[object Object]"]

再來(lái)看看 Map 的,其接收任何類型的鍵名并保留其鍵名類型 (此處簡(jiǎn)單舉例,詳細(xì)可看文章開頭Map基本使用)

const map = new Map();
map.set(1, 'value');
map.set(true, 'value');
map.set({'key': 'value'}, 'value');
for (const key of map.keys()) {
  console.log(key);
}
// 1
// true
// {key: "value"}

//除此之外,Map還支持正則作為鍵名
map.set(/^1[3456789]\d{9}$/,'手機(jī)號(hào)正則')
//Map(1) {/^1[3456789]\d{9}$/ => "手機(jī)號(hào)正則"}

Map支持正則表達(dá)式作為鍵名,這在Object是不被允許的直接報(bào)錯(cuò)

原型 Prototype

Object不同于Map,它不僅僅是表面所看到的。Map只包含你所定義的鍵值對(duì),但是Object對(duì)象具有其原型中的一些內(nèi)置屬性

const newObject = {};
newObject.constructor; // ? Object() { [native code] }

如果操作不當(dāng)沒(méi)有正確遍歷對(duì)象屬性,可能會(huì)導(dǎo)致出現(xiàn)問(wèn)題,產(chǎn)生你意料之外的 bug
[圖片上傳失敗...(image-24e110-1594080339143)]
[圖片上傳失敗...(image-d26148-1594080339143)]

const countWords = (words) => {
  const counts = { };
  for (const word of words) {
    counts[word] = (counts[word] || 0) + 1;
  }
  return counts;
};
const counts = countWords(['constructor', 'creates', 'a', 'bug']);
// {constructor: "function Object() { [native code] }1", creates: 1, a: 1, bug: 1}

這個(gè)例子靈感來(lái)源于《Effective TypeScript》一書

迭代器

Map 是可迭代的,可以直接進(jìn)行迭代,例如forEach循環(huán)或者for...of...循環(huán)

//forEach
const map = new Map();
map.set('key1', 'value1');
map.set('key2', 'value2');
map.set('key3', 'value3');
map.forEach((value, key) => {
  console.log(key, value);
});
// key1 value1
// key2 value2
// key3 value3

//for...of...
for(const entry of map) {
  console.log(entry);
}
// ["key1", "value1"]
// ["key2", "value2"]
// ["key3", "value3"]

但是對(duì)于Object是不能直接迭代的,當(dāng)你嘗試迭代將導(dǎo)致報(bào)錯(cuò)
[圖片上傳失敗...(image-ec2108-1594080339143)]

const object = {
  key1: 'value1',
  key2: 'value2',
  key3: 'value3',
};
for(const entry of object) {
  console.log(entry);
}
// Uncaught TypeError: object is not iterable

這時(shí)候你就需要一個(gè)額外的步驟來(lái)檢索其鍵名、鍵值或者鍵值對(duì)

for(const key of Object.keys(object)) {
  console.log(key);
}
// key1
// key2
// key3

for(const value of Object.values(object)) {
  console.log(value);
}
// value1
// value2
// value3

for(const entry of Object.entries(object)) {
  console.log(entry);
}
// ["key1", "value1"]
// ["key2", "value2"]
// ["key3", "value3"]

for(const [key,value] of Object.entries(object)) {
  console.log(key,value);
}
//"key1", "value1"
//"key2", "value2"
//"key3", "value3"

當(dāng)然也可以使用for...in...進(jìn)行遍歷循環(huán)鍵名

for(const key in object) {
  console.log(key);
}
// key1
// key2
// key3

元素順序和長(zhǎng)度

Map 保持對(duì)長(zhǎng)度的跟蹤,使其能夠在O(1)復(fù)雜度中進(jìn)行訪問(wèn)

const map = new Map();
map.set('key1', 'value1');
map.set('key2', 'value2');
map.set('key3', 'value3');
map.size; // 3

而另一方面,對(duì)于Object而言,想要獲得對(duì)象的屬性長(zhǎng)度,需要手動(dòng)對(duì)其進(jìn)行迭代,使其為O(n)復(fù)雜度,屬性長(zhǎng)度為n

在上文提及的示例中,我們可以看到Map始終保持按插入順序返回鍵名。但Object卻不是。從 ES6 開始,StringSymbol鍵是按順序保存起來(lái)的,但是通過(guò)隱式轉(zhuǎn)換保存成String的鍵就是亂序的

const object = { };
object['key1'] = 'value1';
object['key0'] = 'value0';
object; // {key1: "value1", key0: "value0"}
object[20] = 'value20';
object; // {20: "value20", key1: "value1", key0: "value0"}

Object.keys(object).length; //3

Object/Map 應(yīng)用場(chǎng)景

如上就是 MapObject 的基本區(qū)別,在解決問(wèn)題考慮兩者的時(shí)候就需要考慮兩者的區(qū)別。

  • 當(dāng)插入順序是你解決問(wèn)題時(shí)需要考慮的,并且當(dāng)前需要使用除 StringSymbol 以外的鍵名時(shí),那么 Map 就是個(gè)最佳解決方案
  • 如果需要遍歷鍵值對(duì)(并且需要考慮順序),那我覺得還是需要優(yōu)先考慮 Map。
  • Map是一個(gè)純哈希結(jié)構(gòu),而Object不是(它擁有自己的內(nèi)部邏輯)。Map頻繁增刪鍵值對(duì)的場(chǎng)景下表現(xiàn)更好,性能更高。因此當(dāng)你需要頻繁操作數(shù)據(jù)的時(shí)候也可以優(yōu)先考慮 Map
  • 再舉一個(gè)實(shí)際的例子,比如有一個(gè)自定義字段的用戶操作功能,用戶可以通過(guò)表單自定義字段,那么這時(shí)候最好是使用 Map,因?yàn)楹苡锌赡軙?huì)破壞原有的對(duì)象
const userCustomFields = {
  'color':    'blue',
  'size':     'medium',
  'toString': 'A blue box'
};

此時(shí)用戶自定義的 toString 就會(huì)破壞到原有的對(duì)象
Map 鍵名接受任何類型,沒(méi)有影響

function isMap(value) {
  return value.toString() === '[object Map]';
}

const actorMap = new Map();

actorMap.set('name', 'Harrison Ford');
actorMap.set('toString', 'Actor: Harrison Ford');

// Works!
isMap(actorMap); // => true
  • 當(dāng)你需要處理一些屬性,那么 Object 是完全受用的,尤其是需要處理 JSON 數(shù)據(jù)的時(shí)候。由于 Map 可以是任意類型,因此沒(méi)有可以將其轉(zhuǎn)化為 JSON 的原生方法。
var map = new Map()
map.set('key','value')
JSON.stringify(map)  //"{}"
  • 如果需要在對(duì)象中保持獨(dú)有的邏輯和屬性,只能使用 Object
var obj = {
    id: 1,
    name: "前端Sneaker",
    speak: function(){
        return `Object Id: ${this.id}, with Name: ${this.name}`;
    }
}
console.log(obj.speak());//Object Id: 1, with Name: 前端Sneaker.
  • 當(dāng)你需要通正則表達(dá)式判斷去處理一些業(yè)務(wù)邏輯時(shí),Map將是你的最佳解決方案
const actions = ()=>{
  const functionA = ()=>{/*do sth*/}
  const functionB = ()=>{/*do sth*/}
  const functionC = ()=>{/*send log*/}
  returnnewMap([
    [/^guest_[1-4]$/,functionA],
    [/^guest_5$/,functionB],
    [/^guest_.*$/,functionC],
    //...
  ])
}

const onButtonClick = (identity,status)=>{
  let action = [...actions()].filter(([key,value])=>(key.test(`${identity}_${status}`)))
  action.forEach(([key,value])=>value.call(this))
}

利用數(shù)組循環(huán)的特性,符合正則條件的邏輯都會(huì)被執(zhí)行,那就可以同時(shí)執(zhí)行公共邏輯和單獨(dú)邏輯,因?yàn)檎齽t的存在,你可以打開想象力解鎖更多的玩法,更多相關(guān) Map 用法樣例可以查看JavaScript 復(fù)雜判斷的更優(yōu)雅寫法

總結(jié):

Object對(duì)象通??梢院芎玫谋4娼Y(jié)構(gòu)化數(shù)據(jù),但是也有相應(yīng)的局限性:

  1. 鍵名接受類型只能用 String 或者 Symbol
  2. 自定義的鍵名容易與原型繼承的屬性鍵名沖突(例如 toString,constructor 等)
  3. 對(duì)象/正則無(wú)法用作鍵名
    而這些問(wèn)題通過(guò) Map 都可以解決,并且提供了諸如迭代器和易于進(jìn)行大小查找之類的好處

不要將Map作為普通Object的替代品,而應(yīng)該是普通對(duì)象的補(bǔ)充

MDN

dmitripavlutin

medium

最后編輯于
?著作權(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ù)。

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