从一道算法题学习js中Map和Object的使用和区别

163 阅读4分钟

我们都知道,优化算法的时间复杂度的通常做法是:用空间换时间。比如增加一个哈希表来存储遍历过的元素,而有的时候,我们需要在哈希表里面使用对象做它的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将会调用someObjtoString方法将它转换为字符串,可能引起哈希冲突。

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、两者的使用区别

两者的使用区别如下:

MapObject
意外的键(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以获得原生的序列化支持。