TypeScript:Object和Map的区别

概念

什么是Map

Map是一种数据结构(它很特别,是一种抽象的数据结构类型),数据一对对进行存储,其中包含键以及映射到该键的值。并且由于键的唯一性,因此不存在重复的键值对。

Map便是为了快速搜索和查找数据而生的。

例如:

{ (1, "smile"), (2, "cry"), (42, "happy") }

在Map中,每一对数据的格式都为键值对的形式。

❗️注:Map中的键和值可以是任何数据类型,不仅限于字符串或整数。

什么是Object

JavaScript中的常规对象是一种字典类型的数据结构——这意味着它依然遵循与Map类型相同键值对的存储结构。Object中的key,或者我们可以称之为属性,同样是独一无二的并且对应着一个单独的value。

另外,JavaScript中的Object拥有内置原型(prototype)。需要注意的是,JavaScript中几乎所有对象都是Object实例,包括Map。

例如:

{ 1: 'smile', 2: 'cry', 42: 'happy' }

从定义上来看,Object和Map的本质都是以键值对的方式存储数据,但实质上他们之间存在很大的区别——

  • 键: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

1. 仅做数据存储,并且属性仅为字符串或者Symbol 
2. 需要进行序列化转换json传输时 
3. 当做一个对象的实例,需要保留自己的属性和方法时

Map

1. 会频繁更新和删除键值对时 
2. 存储大量数据时,尤其是key类型未知的情况下 
3. 需要频繁进行迭代处理

posted on 2024-03-27 23:51  梁飞宇  阅读(370)  评论(0)    收藏  举报