今天将重点对比另外两个数据结构,即 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。比如下面对象:
因此,根据定义,Object 和 Map 都使用 key-value 来存储数据。 然而它们之间确实有很大的不同,主要表现在以下几个方面:
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 和 Object2.1 构造 Object 的三种方式与数组相似,定义一个 Object 的方式非常简单:
或使用构造方法: var obj = new Object(); //空对象var obj = new Object; //空对象 或者使用 Object.prototype.create:
注:只能在某些特定的情况下使用 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 在通常情况下,与数组相似,尽量避免使用构造函数的方式,主要基于以下几个事实:
2.2 构造 Map 的几种方式创建 Map 只有一种方式,就是使用其内置的构造函数以及 new 语法。 var map = new Map(); //Empty Mapvar map = new Map([ [1, 2], [2, 3],]); // map = {1=>2, 2=>3} 即遵循如下语法:
Map 的构造函数接收一个数组或是一个可遍历的对象作为参数,这个参数内的数据都为键值对结构。如果是数组,则包含两个元素[key, value]。 3.元素访问差异3.1 元素访问对于 Map,获取元素主要通过方法 Map.prototype.get(key)实现,这意味着必须先知道该值所对应的 key。比如下面代码: map.get(1); Object 类似,要获取到值必须先知道所对应的 key/property,不过使用不同的语法:Object.<key> 或者 Object['key’]:
3.2 判断元素存在判断 Map 中是否存在某个 key: map.has(1);//return boolean value: true/false Object 则需要一些额外的判断:
Map 与 Object 语法很相似,不过 Map 的语法更简单。 注:可以使用Object.prototype.hasOwnProperty()判断Object中是否存在特定的key,它的返回值为true/false,并且只会检查对象上的非继承属性。 4.插入元素Map 支持通过 Map.prototype.set()方法插入元素,该方法接收两个参数:key,value。如果传入已存在的 key,则将会重写该 key 所对应的 value。
同样,为 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 语法:
值得注意的是,很多人提出使用一下方法是否会更好,更节约性能。 obj.id = undefined; 这两种方式在逻辑上有很大差别:
因此在使用 for...in...循环时仍然会遍历到该属性的 key。当然,检查 Object 中是否已存在某属性将在这两种情况下产生两种不同的结果,但以下检查除外:
因此,性能提升在某些情况下并不适合。还有一点,delete 操作符的返回值为 true/false,但其返回值的依据与预想情况有所差异: 对于所有情况都返回 true,除非属性是一个 non-configurable 属性,否则在非严格模式返回 false,严格模式下将抛出异常。Map 有更多内置的删除元素方式,比如:
var isDeleteSucceeded = map.delete(1);console.log(isDeleteSucceeded);//true
Object 要实现 Map 的 clear()方法,需要遍历这个对象的属性,逐个删除。Object 和 Map 删除元素的方法也非常相似。其中删除某个元素的时间复杂度为 O(1),清空元素的时间复杂度为 O(n),n 为 Object 和 Map 的大小。 6.获取大小与 Object 相比,Map 的一个优点是它可以自动更新其大小,可以通过以下方式轻松获得大小: console.log(map.size); 而使用 Object,需要通过 Object.keys()方法计算其大小,该方法返回一个包含所有 key 的数组。
7.元素的迭代Map 有内置的迭代器,Object 没有内置的迭代器。对于如何判断某种类型是否可迭代,可以通过以下方式实现: //typeof <obj>[Symbol.iterator] === “function”console.log(typeof obj[Symbol.iterator]); //undefinedconsole.log(typeof map[Symbol.iterator]); //function 在 Map 中,所有元素可以通过 for...of 方法遍历:
或者使用其内置的 forEach()方法: map.forEach((value, key) => console.log(`key: ${key}, value: ${value}`));//key: 2, value: 3//key: 4, value: 5 但对于 Object,使用 for...in 方法:
或者使用 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 创建时的性能
首先进行对比的是创建 Object 和 Map 时的表现。对于创建的速度表现如下: 我们可以发现创建 Object 的速度会快于 Map。对于内存使用情况则如下: 主要关注其 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; 对于新建元素时的速度表现如下: 可以发现新建元素时,Map 的速度会快于 Object。对于内存使用情况则如下: 通过对比可以发现,在拥有一定数量的元素时, Object 会比 Map 占用多了约 78% 的内存。我也进行了多次的测试,发现在拥有足够的元素时,这个百分比是十分稳定的。所以说,在需要进行很多新增操作,且需要储存许多数据的时候,使用 Map 会更高效。 8.3 读取元素时的性能
对于读取元素时的速度表现如下: 通过对比,我们可以发现 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`; 对于删除元素时的速度表现如下: 我们可以发现在进行删除操作时,Map 的速度会略占优,但整体差别其实并不大。 9.Object vs Map 的应用场景区分尽管Map 相对于 Object 有很多优点,依然存在某些使用 Object 会更好的场景,毕竟 Object 是 JavaScript 中最基础的概念。
参考资料 |
|