我们都知道,优化算法的时间复杂度的通常做法是:用空间换时间。比如增加一个哈希表来存储遍历过的元素,而有的时候,我们需要在哈希表里面使用对象做它的key,但是在js中Map和Object都可以作为哈希表来使用,该使用哪个呢?
1、引子
我们先来看leetcode上的这一道算法题:随机链表的复制
由于链表节点存随机指针,那么,我们在深拷贝的时候,就可能会碰到之前已经访问且拷贝过的节点,这种情况下就不需要再执行深拷贝了,不然会陷入死循环。因此我们需要哈希表判断当前链表节点有没有别访问过,如果访问过,直接取深拷贝之后的值就行了。
/**
* // Definition for a Node.
* function Node(val, next, random) {
* this.val = val;
* this.next = next;
* this.random = random;
* };
*/
/**
* @param {Node} head
* @return {Node}
*/
var copyRandomList = function (head) {
// 哈希存储生成过的节点,哈希值怎么确定?
const dfs = function (head) {
if (!head) {
return null
}
let copeid
if (hashMap.has(head)) {
copeid = hashMap.get(head)
} else {
copeid = new Node(head.val)
hashMap.set(head,copeid)
copeid.next = dfs(head.next)
copeid.random = dfs(head.random)
}
return copeid
}
const hashMap = new Map()
return dfs(head)
};
但是如果我们直接使用Object作为哈希表的话,比如这样:
const hasMap = {}
hasMap[someObj] = copeidObj
由于Object只能使用字符串和Symbol作为key,所以直接使用对象作为key的话,js将会调用someObj的toString方法将它转换为字符串,可能引起哈希冲突。
const hasMap = {};
const someObj = { a: 1 };
hasMap[someObj] = "1213";
console.log(hasMap);
// { '[object Object]': '1213' }
所以我们最好的做法是使用Map作为哈希表,可以使用任意对象作为哈希表的key。
可以参考MDN的解释:Map
The Map
object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value.
2、两者的使用区别
两者的使用区别如下:
Map | Object | |
---|---|---|
意外的键(Accidental Keys) | 默认情况下,Map不包含任何键。它只包含显式放入其中的内容。 | Object有一个原型,所以它包含默认的键,如果你不小心的话,可能会和你自己的键发生冲突。 |
安全性 | Map可以安全地与用户提供的键和值一起使用。 | 在Object上设置用户提供的键值对可能允许攻击者覆盖对象的原型,这可能导致对象注入攻击。与意外键问题一样,这也可以通过使用null-prototype对象来缓解。 |
键的类型 | Map的键可以是任何值(包括函数、对象或任何原始数据类型)。 | String或者Symbol,如果传入的是个对象,会调用对象的toString方法转换为字符串。 |
顺序 | 和插入顺序相同。 | 虽然现在普通对象的键是有序的,但过去并非总是如此,而且顺序很复杂。因此,最好不要依赖于属性顺序。这个顺序最初是在ECMAScript 2015中为自己的属性定义的;ECMAScript 2020也定义了继承属性的顺序。但请注意,没有单一的机制迭代对象的所有属性;每种机制都包含不同的属性子集。 (for-in只包括可枚举的字符串键属性; Object.keys只包括自己的、可枚举的、字符串键的属性; Object.getOwnPropertyNames包含自己的字符串键属性,即使是不可枚举的; Object.getOwnPropertySymbols只获取以Symbol作为键的属性) |
大小 | 通过size属性获取 | 确定Object中的项数更为迂回,效率也较低。一种常见的方法是通过Object.keys()返回的数组长度。 |
迭代 | Map是一个可迭代对象,因此可以直接迭代。 | Object没有实现迭代协议,因此对象不能直接使用JavaScript for…of语句(默认情况下)。 for...in可以迭代获取Object的可枚举键 |
性能 | 在涉及频繁添加和删除键值对的场景中性能更好。 | 没有针对频繁添加和删除键值对进行优化。 |
序列化和解析 | js没有原生支持序列化Map对象,可以这样做 | 原生支持,直接使用JSON.stringify() 和 JSON.parse() . |
3、总结
在js中如果使用哈希表存储数据的时候,最好使用Map而不是使用Object。 但是如果有序列化哈希表的需求时,可以使用Object以获得原生的序列化支持。