分享

什么时候用Object?什么时候用Map?性能对比如何?

 好汉勃士 2022-12-07 发布于广东
文章图片1

今天将重点对比另外两个数据结构,即 Map 和 Object,话不多少,直接开始。

1.Map vs Object 的定义

1.1 什么是 Map?

Map 是一种数据集合类型,更准确的说是抽象数据结构类型。其中,数据以成对的形式存储,其中包含唯一的 key 和映射到该 key 的 value, 并且由于每个存储 key 的唯一性,从而不会存储重复的数据,Map 主要是用来快速查找数据的。

{ (1, 'smile'), (2, 'cry'), (42, 'happy');}

上面对象都是以(key, value)的形式存储,Map 中的 key 和 value 可以是任何数据类型,不限于字符串或整数。

1.2 什么是 Object?

Javascript 中的 Object 是字典类型的数据集合,这意味着它也遵循了像 Map 一样的 key-value 存储概念。 Object 中的每个键、或者我们通常称之为“属性”也是唯一的,并且与单个值相关联。

Javascript 中的 Object 具有内置的原型 prototype,而且 Javascript 中几乎所有对象都是 Object 的实例,包括 Map。比如下面对象:

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

因此,根据定义,Object 和 Map 都使用 key-value 来存储数据。 然而它们之间确实有很大的不同,主要表现在以下几个方面:

  • 键名:在 Object 中,它遵循普通字典的规则。 键必须是简单类型,如整数、字符串或者 Symbols。 但在 Map 中,它可以是任何数据类型(包括对象、数组等)(Map 相当于尝试使用另一个对象作为对象的属性键)
  • 元素顺序:在 Map 中,元素的原始顺序被保留,而在 Object 中,则不是
  • Map 是 Object 的实例,但 Object 绝对不是 Map 的实例
var map = new Map([ [1, 2], [3, 4],]);console.log(map instanceof Object);//truevar obj = new Object();console.log(obj instanceof Map);//false

2.如何构造 Map 和 Object

2.1 构造 Object 的三种方式

与数组相似,定义一个 Object 的方式非常简单:

var obj = {};//空对象var obj = { id: 1, name: 'Test object' };//2个key。id为1,而name为'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的对象CarCar.type = 'Car'; //重写type属性Car.display(); //CarVehicle.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

2.2 构造 Map 的几种方式

创建 Map 只有一种方式,就是使用其内置的构造函数以及 new 语法。

var map = new Map(); //Empty Mapvar map = new Map([ [1, 2], [2, 3],]); // map = {1=>2, 2=>3}

即遵循如下语法:

Map([iterable]);

Map 的构造函数接收一个数组或是一个可遍历的对象作为参数,这个参数内的数据都为键值对结构。如果是数组,则包含两个元素[key, value]。

3.元素访问差异

3.1 元素访问

对于 Map,获取元素主要通过方法 Map.prototype.get(key)实现,这意味着必须先知道该值所对应的 key。比如下面代码:

map.get(1);

Object 类似,要获取到值必须先知道所对应的 key/property,不过使用不同的语法:Object.<key> 或者 Object['key’]:

obj.id; //1obj['id']; //1

3.2 判断元素存在

判断 Map 中是否存在某个 key:

map.has(1);//return boolean value: true/false

Object 则需要一些额外的判断:

var isExist = obj.id === undefined;// orvar isExist = 'id' in obj;// 该方法会检查继承的属性

Map 与 Object 语法很相似,不过 Map 的语法更简单。

注:可以使用Object.prototype.hasOwnProperty()判断Object中是否存在特定的key,它的返回值为true/false,并且只会检查对象上的非继承属性。

4.插入元素

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 并不需要遍历所有数据。

5.删除元素

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 的大小。

6.获取大小

与 Object 相比,Map 的一个优点是它可以自动更新其大小,可以通过以下方式轻松获得大小:

console.log(map.size);

而使用 Object,需要通过 Object.keys()方法计算其大小,该方法返回一个包含所有 key 的数组。

Object.keys(obj).length;

7.元素的迭代

Map 有内置的迭代器,Object 没有内置的迭代器。对于如何判断某种类型是否可迭代,可以通过以下方式实现:

//typeof <obj>[Symbol.iterator] === “functionconsole.log(typeof obj[Symbol.iterator]); //undefinedconsole.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]}//Orfor (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

8.性能对比

好的,问题来了,因为 Map 和 Object 在结构和性能方面都非常相似,Map 相比 Object 具有更多的优势,那是否应该使用 Map 代替 Object?下面将从创建元素、添加元素、获取元素、删除元素等诸多维度来对比下 Object 和 Map 的性能。

8.1 创建时的性能

let n,  n2 = 5;// 速度while (n2--) {  let p1 = performance.now();  n = 10000;  while (n--) {    let o = {};  }  let p2 = performance.now();  n = 10000;  while (n--) {    let m = new Map();  }  let p3 = performance.now();  console.log(    `Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`  );}// 内存class Test {}let test = new Test();test.o = o;test.m = m;

首先进行对比的是创建 Object 和 Map 时的表现。对于创建的速度表现如下:

文章图片2

我们可以发现创建 Object 的速度会快于 Map。对于内存使用情况则如下:

文章图片3

主要关注其 Retained Size,它表示了为其分配的空间。(即删除时释放的内存大小)通过对比我们可以发现,空的 Object 会比空的 Map 占用更少的内。

8.2 新增元素时的性能

console.clear();let n, n2 = 5;let o = {}, m = new Map();// 速度while (n2--) { let p1 = performance.now(); n = 10000; while (n--) { o[Math.random()] = Math.random(); } let p2 = performance.now(); n = 10000; while (n--) { m.set(Math.random(), Math.random()); } let p3 = performance.now(); console.log( `Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms` );}// 内存class Test {}let test = new Test();test.o = o;test.m = m;

对于新建元素时的速度表现如下:

文章图片4

可以发现新建元素时,Map 的速度会快于 Object。对于内存使用情况则如下:

文章图片5

通过对比可以发现,在拥有一定数量的元素时, Object 会比 Map 占用多了约 78% 的内存。我也进行了多次的测试,发现在拥有足够的元素时,这个百分比是十分稳定的。所以说,在需要进行很多新增操作,且需要储存许多数据的时候,使用 Map 会更高效

8.3 读取元素时的性能

let n;let o = {},  m = new Map();n = 10000;while (n--) {  o[Math.random()] = Math.random();}n = 10000;while (n--) {  m.set(Math.random(), Math.random());}let p1 = performance.now();for (key in o) {  let k = o[key];}let p2 = performance.now();for ([key] of m) {  let k = m.get(key);}let p3 = performance.now();`Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`;

对于读取元素时的速度表现如下:

文章图片6

通过对比,我们可以发现 Object 略占优势,但总体差别不大。

8.4 删除元素时的性能

不知道大家是否听说过 delete 操作符性能低下,甚至有很多时候为了性能,会宁可将值设置为 undefined 而不使用 delete 操作符的说法。但其实在 v8 近来的优化下,它的效率已经提升许多了。

let n;let o = {}, m = new Map();n = 10000;while (n--) { o[Math.random()] = Math.random();}n = 10000;while (n--) { m.set(Math.random(), Math.random());}let p1 = performance.now();for (key in o) { delete o[key];}let p2 = performance.now();for ([key] of m) { m.delete(key);}let p3 = performance.now();`Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`;

对于删除元素时的速度表现如下:

文章图片7

我们可以发现在进行删除操作时,Map 的速度会略占优,但整体差别其实并不大。

9.Object vs 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.
  • JSON 直接支持 Object,但尚未支持 Map。因此,在某些必须使用 JSON 的情况下,应将 Object 视为首选。
  • Map 是一个纯哈希结构,而 Object 不是(它拥有自己的内部逻辑)。使用 delete 对 Object 的属性进行删除操作存在很多性能问题。所以,针对于存在大量增删操作的场景,使用 Map 更合适。
  • 不同于 Object,Map 会保留所有元素的顺序,Map 结构是在基于可迭代的基础上构建的,所以如果考虑到元素迭代或顺序,使用 Map 更好,它能够确保在所有浏览器中的迭代性能。
  • Map 在存储大量数据的场景下表现更好,尤其是在 key 为未知状态,并且所有 key 和所有 value 分别为相同类型的情况下。

参考资料

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多