眾所周知 JavaScript 的 Object 和 Map 這兩種數據結構很相似,
但深究底層原理來看,這兩者本質上還是存在了不少差異,通過區別比較能幫助我們更好地理解它們的用處和使用場景。
鍵類型
Object
Object 的鍵必須是 String 或 Symbol 類型,並默認調用 toString 方法將鍵轉化為 String 類型,因此可能會存在同名鍵覆蓋問題。
注:Array和Function本質是對Object的繼承,因此都有對應的toString方法。
對象鍵
將對象作為鍵時會調用 Object.toString 方法將其轉化為對象字符串 ("[object Object]")。
({}.toString()); // "[object Object]"
var obj = {};
obj[{}] = "ok";
console.log(JSON.stringifpwdy(obj)); // {"[object Object]":"ok"}
數組鍵
將數組作為鍵時會調用 Array.toString 方法將其轉化為空字符串 ("")。
[].toString(); // ""
var obj = {};
obj[[]] = "ok";
console.log(JSON.stringify(obj)); // {"":"ok"}
函數鍵
將函數作為鍵時會調用 Function.toString 方法將其轉化為函數字符串 ("() => {}")。
(function test() => {}).toString(); // "() => {}"
var obj = {};
obj[() => {}] = "ok";
console.log(JSON.stringify(obj)); // {"() => {}":"ok"}
Map
Map 支持任意類型的鍵。
Map objects are collections of key/value pairs where both the keys and values may be arbitrary ECMAScript language values.
鍵唯一性
Object 同名鍵覆蓋
由於 Object 的鍵默認會調用 toString 方法,因此當前鍵如果是空對象({})或者空數組([])的話,多次賦值會出現被覆蓋的情況。
var obj = {};
obj[{}] = "step1";
obj[{}] = "step2";
console.log(JSON.stringify(obj)); // {"[object Object]":"step2"}
Map 唯一鍵
在 Map 中每個鍵都是唯一的,在存儲過程中 Map 會對存入鍵的 類型 或 引用 進行比較。假設當前 Map 存入了兩個空對象({}),兩者類型相同,但在 棧 中引用的內存地址不同,那麼 Map 就會認定為是兩個獨立鍵,示例如下:
遍歷次序
Object 無序
在遍歷 Object 後得到的結果是一個無序列表。
var obj = { 1: 1, 2: 2, a: "a", f: "f" };
console.log(Object.keys(obj)); // ["1", "2", "a", "f"]
obj = { a: "a", 1: 1, 2: 2, f: "f" };
console.log(Object.keys(obj)); // ["1", "2", "a", "f"]
Map 有序
在遍歷 Map 後得到的結果是一個有序列表。
var map = new Map();
map.set(1, 1);
map.set("a", "a");
map.set(2, 2);
console.log([...map.values()]); // [1, "a", 2]
可遍歷
Object
Object 沒有實現遍歷器(@@iterator)接口,無法使用 for of 遍歷,但可以用 for in 等方法遍歷。當然,Object 原生不支持但可以擴展 @@iterator 實現遍歷,詳見 iterator。
var obj = { a: "a", 1: 1, 2: 2, f: "f" };
for (key in obj) {
if (obj.hasOwnProperty(key)) {
console.log(key);
}
}
console.log(Object.keys(obj)); // ["1", "2", "a", "f"]
Map
Map 內部實現了遍歷器(@@iterator)接口,可以使用 for of 遍歷。
Map.prototype [ @@iterator ] ( )
var map = new Map();
map.set(1, 1);
map.set("a", "a");
map.set(2, 2);
for (item of map) {
console.log(item);
}
繼承關係
從原型鏈繼承結構中,我們可以看到 Map 底層實際上是繼承自 Object ,即 Map 是 Object 的實例對象,反之 Object 並不是 Map 的實例對象。
參考文檔
ECMA 官方文檔