【译】Object与Map的异同及使用场景
栏目: JavaScript · 发布时间: 5年前
内容简介:这是介绍Array,Set,Object,Map系列的第二篇译文。原文链接:戳这里按照惯例,详细的API补充在在文章底部。
这是介绍Array,Set,Object,Map系列的第二篇译文。
原文链接:戳这里
按照惯例,详细的API补充在在文章底部。
你可能想问,为什么要单独将Object和Map进行对比,而不是对比Map,Array,或是Object和Set?不同于其它两组,Map和Object有非常多相似的地方需要我们去更深入的了解和对比,才能分析出他们分别更适合的应用场景。
概念
什么是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中最基础的概念。
get()
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并不能实现这样的数据结构)
delete
总结
如何选择Object和Map取决于你要使用的数据类型以及操作。
当我们只需要一个简单的可查找存储结构时,Map相比Object更具优势,它提供了所有基本操作。但在任何意义上,Map都不能替代Object。因为在Javascript中,Object毕竟不仅仅是一个普通的哈希表(因此Object不应该用作普通哈希表,它会浪费很多资源)。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 【译】Array与Set的异同及使用场景
- C++/Golang的数组类型异同
- DevOps与敏捷异同 - DZone DevOps
- Dart 入门 & 与 ts 类型系统的异同
- 浅谈集群、分布式、微服务的异同
- 服务容错模式:舱壁模式、熔断器的异同点
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。