【譯】Object與Map的異同及使用場景

這是介紹Array,Set,Object,Map系列的第二篇譯文。
原文連接:戳這裏
按照慣例,詳細的API補充在在文章底部。 javascript

你可能想問,爲何要單獨將Object和Map進行對比,而不是對比Map,Array,或是Object和Set?不一樣於其它兩組,Map和Object有很是多類似的地方須要咱們去更深刻的瞭解和對比,才能分析出他們分別更適合的應用場景。java

概念

什麼是Map

Map是一種數據結構(它很特別,是一種抽象的數據結構類型),數據一對對進行存儲,其中包含鍵以及映射到該鍵的值。而且因爲鍵的惟一性,所以不存在重複的鍵值對。
Map即是爲了快速搜索和查找數據而生的。
例如:{(1, "smile"), (2, "cry"), (42, "happy")}es6

在Map中,每一對數據的格式都爲鍵值對的形式。數組

:Map中的鍵和值能夠是任何數據類型,不只限於字符串或整數。瀏覽器

什麼是Object

JavaScript中的常規對象是一種字典類型的數據結構——這意味着它依然遵循與Map類型相同鍵值對的存儲結構。Object中的key,或者咱們能夠稱之爲屬性,一樣是獨一無二的而且對應着一個單獨的value。bash

另外,JavaScript中的Object擁有內置原型(prototype)。須要注意的是,JavaScript中幾乎全部對象都是Object實例,包括Map。
例如:{1: 'smile', 2: 'cry', 42: 'happy'}數據結構

從定義上來看,Object和Map的本質都是以鍵值對的方式存儲數據,但實質上他們之間存在很大的區別——app

  • 鍵:Object遵循普通的字典規則,鍵必須是單一類型,而且只能是整數、字符串或是Symbol類型。但在Map中,key能夠爲任意數據類型(Object, Array等)。(你能夠嘗試將一個對象設置爲一個Object的key,看看最終的數據結構)
  • 元素順序:Map會保留全部元素的順序,而Object並不會保證屬性的順序。(若有疑問可參考:連接
  • 繼承:Map是Object的實例對象,而Object顯然不多是Map的實例對象。
var map = new Map([[1,2],[3,4]]);
console.log(map instanceof Object); //true
var obj = new Object();
console.log(obj instanceof Map); //false
複製代碼

如何構建

Object

與數組類似,定義一個Object的方式很是簡單直接:函數

var obj = {}; //空對象
var obj = {id: 1, name: "Test object"}; 
//2 keys here: id maps to 1, and name maps to "Test object"
複製代碼

或使用構造方法:性能

var obj = new Object(); //空對象
var obj = new Object; //空對象
複製代碼

或者使用Object.prototype.create

var obj = Object.create(null); //空對象
複製代碼


你只能在某些特定的狀況下使用Object.prototype.create,好比:

  • 你但願繼承某個原型對象,而無需定義它的構造函數。
var Vehicle = {
    type: "General",
    display: function(){console.log(this.type);}
}
var Car = Object.create(Vehicle); //建立一個繼承自Vehicle的對象Car
Car.type = "Car";  //重寫type屬性
Car.display(); //Car
Vehicle.display(); //General
複製代碼

在一般狀況下,與數組類似,儘可能避免使用構造函數的方式,理由以下:

  • 構造函數會寫更多代碼
  • 性能更差
  • 更加混亂更容易引發程序錯誤,例如:
var obj = new Object(id: 1, name: "test") //顯然的語法錯誤

var obj1 = {id: 1, name: "test"};
var obj2 = new Object(obj1); //obj1與obj2指向同一個對象
obj2.id = 2;
console.log(obj1.id); //2
複製代碼

Map

建立Map只有一種方式,就是使用其內置的構造函數以及new語法。

var map = new Map(); //Empty Map
var map = new Map([[1,2],[2,3]]); // map = {1=>2, 2=>3}
複製代碼

語法:

Map([iterable])

Map的構造函數接收一個數組或是一個可遍歷的對象做爲參數,這個參數內的數據都爲鍵值對結構。若是是數組,則包含兩個元素[key, value]

訪問元素

  • 對於Map,獲取元素要經過方法Map.prototype.get(key)實現,這意味着咱們必須先知道該值所對應的key
map.get(1);
複製代碼
  • Object相似,要獲取到值必須先知道所對應的key/property,不過使用不一樣的語法:Object.<key> and Object[‘key’]
obj.id //1
obj['id'] //1
複製代碼
  • 判斷Map中是否存在某個key
map.has(1);//return boolean value: true/false
複製代碼
  • Object則須要一些額外的判斷
var isExist = obj.id === undefined; 

// or
var isExist = 'id' in obj; // 該方法會檢查繼承的屬性
複製代碼

Map與Object語法很類似,不過Map的語法更簡單。

:咱們可使用Object.prototype.hasOwnProperty()判斷Object中是否存在特定的key,它的返回值爲true/false,而且只會檢查對象上的非繼承屬性

插入元素

  • Map支持經過Map.prototype.set()方法插入元素,該方法接收兩個參數:key,value。若是傳入已存在的key,則將會重寫該key所對應的value。
map.set(4,5); 
複製代碼
  • 一樣,爲Object添加屬性可使用下面的方法
obj['gender'] = 'female'; //{id: 1, name: "test", gender: "female"}
obj.gender = male; // 重寫已存在的屬性
//{id: 1, name: "test", gender: "male"}
複製代碼

正如你所看到的,歸功於其數據結構,兩種插入元素方法的時間複雜度都爲O(1),檢索key並不須要遍歷全部數據。

刪除元素

Object並無提供刪除元素的內置方法,咱們可使用delete語法:

delete obj.id;
複製代碼

值得注意的是,不少人提出使用一下方法是否會更好,更節約性能。

obj.id = undefined
複製代碼

這兩種方式在邏輯上有很大差異:

  • delete會徹底刪除Object上某個特有的屬性
  • 使用obj[key] = undefined只會改變這個key所對應的value爲undefined,而該屬性仍然保留在對象中。

所以在使用for...in...循環時仍然會遍歷到該屬性的key。

固然,檢查Object中是否已存在某屬性將在這兩種狀況下產生兩種不一樣的結果,但如下檢查除外:

obj.id === undefined; //結果相同
複製代碼

所以,性能提高在某些狀況下並不適合。

還有一點,delete操做符的返回值爲true/false,但其返回值的依據與預想狀況有所差別:
對於全部狀況都返回true,除非屬性是一個non-configurable屬性,不然在非嚴格模式返回false,嚴格模式下將拋出異常。

Map有更多內置的刪除元素方式,好比:

  • delete(key)用於從Map中刪除特定key所對應的value,該方法返回一個布爾值。若是目標對象中存在指定的key併成功刪除,則返回true;若是對象中不存在該key則返回false
var isDeleteSucceeded = map.delete(1);
console.log(isDeleteSucceeded); //true- 
複製代碼
  • clear()——清空Map中全部元素。
map.clear();
複製代碼

Object要實現Map的clear()方法,須要遍歷這個對象的屬性,逐個刪除。

Object和Map刪除元素的方法也很是類似。其中刪除某個元素的時間複雜度爲O(1),清空元素的時間複雜度爲O(n),n爲Object和Map的大小。

獲取大小

與Object相比,Map的一個優勢是它能夠自動更新其大小,咱們能夠經過如下方式輕鬆得到:

console.log(map.size);
複製代碼

而使用Object,咱們須要經過Object.keys()方法計算其大小,該方法返回一個包含全部key的數組。

console.log(Object.keys(obj).length);
複製代碼

元素的迭代

Map有內置的迭代器,Object沒有內置的迭代器。
補充:如何判斷某種類型是否可迭代,能夠經過如下方式實現

//typeof <obj>[Symbol.iterator] === 「function」
console.log(typeof obj[Symbol.iterator]); //undefined
console.log(typeof map[Symbol.iterator]); //function
複製代碼

在Map中,全部元素能夠經過for...of方法遍歷:

//For map: { 2 => 3, 4 => 5 }
for (const item of map){
    console.log(item); 
    //Array[2,3]
    //Array[4,5]
}
//Or
for (const [key,value] of map){
    console.log(`key: ${key}, value: ${value}`);
    //key: 2, value: 3
    //key: 4, value: 5
}
複製代碼

或者使用其內置的forEach()方法:

map.forEach((value, key) => console.log(`key: ${key}, value: ${value}`));
//key: 2, value: 3
//key: 4, value: 5
複製代碼

但對於Object,咱們使用for...in方法

//{id: 1, name: "test"}
for (var key in obj){
   console.log(`key: ${key}, value: ${obj[key]}`);
   //key: id, value: 1
   //key: name, value: test
}
複製代碼

或者使用Object.keys(obj)只能獲取全部key並進行遍歷

Object.keys(obj).forEach((key)=> console.log(`key: ${key}, value: ${obj[key]}`));
//key: id, value: 1
//key: name, value: test
複製代碼

好的,問題來了,由於它們在結構和性能方面都很是類似,Map相比Object具備更多的優點,那咱們是否應該更常使用Map代替Object?

Object和Map的應用場景

儘管,Map相對於Object有不少優勢,依然存在某些使用Object會更好的場景,畢竟Object是JavaScript中最基礎的概念。

  • 若是你知道全部的key,它們都爲字符串或整數(或是Symbol類型),你須要一個簡單的結構去存儲這些數據,Object是一個很是好的選擇。構建一個Object並經過知道的特定key獲取元素的性能要優於Map(字面量 vs 構造函數,直接獲取 vs get()方法)。
  • 若是須要在對象中保持本身獨有的邏輯和屬性,只能使用Object。
var obj = {
    id: 1, 
    name: "It's Me!", 
    print: function(){ 
        return `Object Id: ${this.id}, with Name: ${this.name}`;
    }
}
console.log(obj.print());//Object Id: 1, with Name: It's Me.
複製代碼

(你可使用Map進行嘗試,然而Map並不能實現這樣的數據結構)

  • JSON直接支持Object,但還沒有支持Map。所以,在某些咱們必須使用JSON的狀況下,應將Object視爲首選。
  • Map是一個純哈希結構,而Object不是(它擁有本身的內部邏輯)。使用delete對Object的屬性進行刪除操做存在不少性能問題。因此,針對於存在大量增刪操做的場景,使用Map更合適。
  • 不一樣於Object,Map會保留全部元素的順序。Map結構是在基於可迭代的基礎上構建的,因此若是考慮到元素迭代或順序,使用Map更好,它可以確保在全部瀏覽器中的迭代性能。
  • Map在存儲大量數據的場景下表現更好,尤爲是在key爲未知狀態,而且全部key和全部value分別爲相同類型的狀況下。

總結

如何選擇Object和Map取決於你要使用的數據類型以及操做。

當咱們只須要一個簡單的可查找存儲結構時,Map相比Object更具優點,它提供了全部基本操做。但在任何意義上,Map都不能替代Object。由於在Javascript中,Object畢竟不只僅是一個普通的哈希表(所以Object不該該用做普通哈希表,它會浪費不少資源)。

其餘資料連接:
Object - MDN - Mozilla
Map - MDN - Mozilla
ES6入門 - 阮一峯

相關文章
相關標籤/搜索