用 map 代替純 JavaScript 對象

JavaScript 普通對象 {key:'value'} 可用於保存結構化數據數組

可是我發現很煩人的一件事,對象的鍵必須是字符串(或者不多使用的符號)。函數

若是用數字做爲鍵會怎麼樣呢?在這種狀況下沒有錯誤:ui

const names = {
  1: 'One',
  2: 'Two'
}
Object.keys(names); // ['1','2']
複製代碼

JavaScript 只是對象的鍵隱式轉換爲字符串。這是一件棘手的事情,由於你失去了類型的一致性。在本文中,我將介紹ES2015中提供的JavaScriptMap對象如何解決許多普通對象的問題,包括將鍵轉換爲字符串。spa

一、map可接受任意類型的鍵

如上所述,若是對象的鍵不是字符串或者符號,則js會將其隱式轉換爲字符串。 幸運的是,map在鍵類型上不存在問題:code

const numbersMap = new Map();
numbersMap.set(1,'one');
numbersMap.set(2,'two');

[...numbersMap.key()] // => [1,2]
複製代碼

1 和 2 是numbersMap 中的鍵,這些鍵的類型number保持不變 你能夠在map中使用任何鍵類型:數字,布爾以及經典的字符串和符號。對象

const booleansMap = new Map();
booleansMap.set(true,'Yep');
booleansMap.set(false,'Nope');
[...booleansMap.key()] // [true,fasle]
複製代碼

booleansMap 用布爾值做爲鍵沒有問題。繼承

一樣,布爾鍵在普通對象中不起做用。 讓咱們超越解界限:你能把整個對象用做map中的鍵嗎? 固然能夠!ip

1.1 把對象做爲鍵

假設你須要存儲一些與對象相關的數據,可是不能把這些數據附加到對象自己。內存

不能用普通對象這樣作。字符串

一種解決辦法就是用一組對象值元組:

const foo = {name:'foo'}
const bar = {name:'bar'}

const kindOfMap = [
  [foo,'Foo related data'],
  [bar,'Bar related data']
]
複製代碼

kindOfMap 是一個包含一對對象和關聯值的數組。

這種方式的最大的問題在於經過鍵訪問值的時間複雜度是O(n),必須遍歷整個數組才能經過鍵得到所須要的值

function getByKey(kindOfMap,key){
  for(const [k,v] of kindOfMap){
    if(key === k){
      return v
    }
  }
  return undefined
}
getByKey(kindOfMap,foo); // 'Foo related data'
複製代碼

使用 WeakMap(Map)的專用版本,無需爲此煩惱,它接收把對象做爲鍵。

Map 和 WeakMap 之間的主要區別是後者容許對做爲鍵的對象進行垃圾回收,從而防止內存泄漏。

把上面的代碼重構使用WeakMap的代碼付出的代價微不足道

const foo = {name: 'foo'}
const bar = {name: 'bar'}

const mapOfObject = new WeakMap();

mapOfObject.set(foo,'Foo related data')
mapOfObject.set(bar,'Bar related data')

mapOfObject.get(foo) // => 'Foo related data'
複製代碼

與Map 相對,WeapMap 僅僅接受把對象做爲鍵。

二、map對鍵名沒有限制

JavaScript中的任何對象都從其原型對象繼承屬性。普通的JavaScript 對象也是如此。

若是覆蓋從原型繼承的屬性,則可能會破壞依賴於這些原型屬性的代碼;

function isPlainObject(value){
  return value.toString() === '[object Object]'
}
const actor = {
  name: 'Harrison Ford',
  toString: 'Actor: Harrison Ford'
}

// Does not work!
isPlainObject(actor); // TypeError: value.toString is not a function
複製代碼

在對象 actor 上定義的屬性 toString 覆蓋了從原型上面繼承的 toString() 方法,由於它依賴於toString() 方法,因此這破壞了 isObject();

檢查普通對象從原型繼承的屬性和方法列表 要避免使用這些名稱定義自定義屬性。

Map 沒有這個限制鍵的名稱不受限制:

function isMap(value){
  return value.toSring() === '[object Map]'
}
const actorMap = new Map();

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

// Works!
isMap(actorMap); // => true
複製代碼

無論 actorMap 是否具備 名稱爲 toString的屬性,方法 toString() 都能正常工做。

三、map是能夠迭代的

爲了遍歷普通對象的屬性,你必須使用其餘的輔助的靜態函數 例如object.key()或者Object.entries()

const colorsHex = {
  'white': '#FFFFFF',
  'black': '#000000'
};

for (const [color, hex] of Object.entries(colorsHex)) {
  console.log(color, hex);
}
// 'white' '#FFFFFF'
// 'black' '#000000'
複製代碼

Object.entries(colorsHex) 返回從對象提取的鍵值對數組。

可是map 自己就是能夠迭代的:

const colorsHexMap = new Map();

colorsHexMap.set('white', '#FFFFFF');
colorsHexMap.set('black', '#000000');

for (const [color, hex] of colorsHexMap) {
  console.log(color, hex);
}
// 'white' '#FFFFFF'
// 'black' '#000000'
複製代碼

colorsHexMap 是能夠迭代的,你能夠在任何可迭代的地方使用它:for()循環、展開運算符[...map]等。

map還提供了返回迭代的其餘方法,map.key()遍歷鍵 map.values() 遍歷值。

四、map的大小

普通對象的另外一個問題是你沒法輕鬆肯定其擁有的屬性數量:

const exams = {
  'John Smith': '10 points',
  'Jane Doe': '8 points',
};

Object.keys(exams).length; // => 2
複製代碼

要肯定 exams 的大小,你必須經過它全部鍵來肯定它們的數量。

map 提供了一種替代方法,經過它的訪問器屬性 size 計算鍵值對:

const examsMap = new Map([
  ['John Smith', '10 points'],
  ['Jane Doe', '8 points'],
]);
  
examsMap.size; // => 2
複製代碼

五、總結

普通的 JavaScript 對象一般能夠很好地保存結構化數據。可是它們有一些限制:

  • 一、只能用字符串或符號用做鍵
  • 二、本身的對象屬性可能會與從原型繼承的屬性鍵衝突(例如,toString,constructor 等)。
  • 三、對象不能用做鍵

全部這些問題均可以經過 map 輕鬆解決。並且它們提供了諸如迭代器和易於進行大小查找之類的好處。

不要將 map 視爲普通對象的替代品,而應視爲補充。

相關文章
相關標籤/搜索