js的Map和Object对比

前言

在日常的 JavaScript 项目中,我们最常用到的数据结构就是各种形式的键值对格式了(key-value pair)。在 JavaScript 中,除了最基础的 Object 是该格式外,ES6 新增的 Map 也同样是键值对格式。它们的用法在很多时候都十分接近。不知道有没有人和我一样纠结过该选择哪个去使用呢?在本菜最近的项目中,我又遇到了这样的烦恼,索性一不做二不休,去对比一下究竟该使用哪一个。

本文将会探讨一下 Object 和 Map 的不同,从多个角度对比一下 Object 和 Map

  • 用法的区别:在某些情况下的用法会截然不同

  • 句法的区别:创建以及增删查改的句法区别

  • 性能的区别:速度和内存占用情况

希望读完本文的你可以在日后的项目中做出更为合适的选择。

用法对比

  1. 对于 Object 而言,它键(key)的类型只能是字符串,数字或者 Symbol;而对于 Map 而言,它可以是任何类型。(包括 Date,Map,或者自定义对象)

  2. Map 中的元素会保持其插入时的顺序;而 Object 则不会完全保持插入时的顺序,而是根据如下规则进行排序:

  • 非负整数会最先被列出,排序是从小到大的数字顺序

  • 然后所有字符串,负整数,浮点数会被列出,顺序是根据插入的顺序

  • 最后才会列出 SymbolSymbol 也是根据插入的顺序进行排序的

  • 读取 Map 的长度很简单,只需要调用其 .size() 方法即可;而读取 Object 的长度则需要额外的计算:Object.keys(obj).length

  • Map 是可迭代对象,所以其中的键值对是可以通过 for of 循环或 .foreach() 方法来迭代的;而普通的对象键值对则默认是不可迭代的,只能通过 for in 循环来访问(或者使用 Object.keys(o)、Object.values(o)、Object.entries(o) 来取得表示键或值的数字)迭代时的顺序就是上面提到的顺序。

     
    1. const o = {};

    2. const m = new Map();

    3. o[Symbol.iterator] !== undefined; // false

    4. m[Symbol.iterator] !== undefined; // true

  • 在 Map 中新增键时,不会覆盖其原型上的键;而在 Object 中新增键时,则有可能覆盖其原型上的键:

     
    1. Object.prototype.x = 1;

    2. const o = {x:2};

    3. const m = new Map([[x,2]]);

    4. o.x; // 2,x = 1 被覆盖了

    5. m.x; // 1,x = 1 不会被覆盖

  • JSON 默认支持 Object 而不支持 Map。若想要通过 JSON 传输 Map 则需要使用到 .toJSON() 方法,然后在 JSON.parse() 中传入复原函数来将其复原。

    对于 JSON 这里就不具体展开了,有兴趣的朋友可以看一下这:JSON 的序列化和解析

     
    1. const o = {x:1};

    2. const m = new Map([['x', 1]]);

    3. const o2 = JSON.parse(JSON.stringify(o)); // {x:1}

    4. const m2 = JSON.parse(JSON.stringify(m)) // {}

  • 句法对比

    创建时的区别

    Obejct

     
    1. const o = {}; // 对象字面量

    2. const o = new Object(); // 调用构造函数

    3. const o = Object.create(null); // 调用静态方法 Object.create 

    对于 Object 来说,我们在 95%+ 的情况下都会选择对象字面量,它不仅写起来最简单,而且相较于下面的函数调用,在性能方面会更为高效。对于构建函数,可能唯一使用到的情况就是显式的封装一个基本类型;而 Object.create 可以为对象设定原型。

    Map

    const m = new Map(); // 调用构造函数
    

    和 Object 不同,Map 没有那么多花里胡哨的创建方法,通常只会使用其构造函数来创建。

    除了上述方法之外,我们也可以通过 Function.prototype.apply()、Function.prototype.call()、reflect.apply()、Reflect.construct() 方法来调用 Object 和 Map 的构造函数或者  Object.create() 方法,这里就不展开了。

    新增/读取/删除元素时的区别

    Obejct

     
    1. const o = {};

    2. //新增/修改

    3. o.x = 1;

    4. o['y'] = 2;

    5. //读取

    6. o.x; // 1

    7. o['y']; // 2

    8. //或者使用 ES2020 新增的条件属性访问表达式来读取

    9. o?.x; // 1

    10. o?.['y']; // 2

    11. //删除

    12. delete o.b;

    对于新增元素,看似使用第一种方法更为简单,不过它也有些许限制:

    • 属性名不能包含空格和标点符号

    • 属性名不能以数字开头

    对于条件属性访问表达式的更多内容可以看一下这:条件属性访问表达式

    Map

     
    1. const m = new Map();

    2. //新增/修改

    3. m.set('x', 1);

    4. //读取

    5. map.get('x');

    6. //删除

    7. map.delete('b');

    对于简单的增删查改来说,Map 上的方法使用起来也是十分便捷的;不过在进行联动操作时,Map 中的用法则会略显臃肿:

     
    1. const m = new Map([['x',1]]);

    2. // 若想要将 x 的值在原有基础上加一,我们需要这么做:

    3. m.set('x', m.get('x') + 1);

    4. m.get('x'); // 2

    5.  
    6. const o = {x: 1};

    7. // 在对象上修改则会简单许多:

    8. o.x++;

    9. o.x // 2

    性能对比

    接下来我们来讨论一下 Object 和 Map 的性能。不知道各位有没有听说过 Map 的性能优于 Object 的说法,我反正是见过不少次,甚至在 JS 高程四中也提到了 Map 对比 Object 时性能的优势;不过对于性能的概括都十分的笼统,所以我打算做一些测试来对比一下它们的区别。

    测试方法

    在这里我进行的对于性能测试的都是基于 v8 引擎的。速度会通过 JS 标准库自带的 performance.now() 函数来判断,内存使用情况会通过 Chrome devtool 中的 memory 来查看。

    对于速度测试,因为单一的操作速度太快了,很多时候 performance.now() 会返回 0。所以我进行了 10000 次的循环然后判断时间差。因为循环本身也会占据一部分时间,所以以下的测试只能作为一个大致的参考。

    创建时的性能

    测试用的代码如下

     
    1. let n,  n2 = 5;

    2. // 速度

    3. while (n2--) {

    4.   let p1 = performance.now();

    5.   n = 10000;

    6.   while (n--) { let o = {}; }

    7.   let p2 = performance.now();

    8.   n = 10000;

    9.   while (n--) { let m = new Map(); }

    10.   let p3 = performance.now();

    11.   console.log(`Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`);

    12. }

    13. // 内存

    14. class Test {}

    15. let test = new Test();

    16. test.o = o;

    17. test.m = m;

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

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

    我们主要关注其 Retained Size,它表示了为其分配的空间。(即删除时释放的内存大小)

    通过对比我们可以发现,空的 Object 会比空的 Map 占用更少的内。所以这一轮 Object 赢得一筹。

    新增元素时的性能

    测试用的代码如下

     
    1. console.clear();

    2. let n,  n2 = 5;

    3. let o = {}, m = new Map();

    4. // 速度

    5. while (n2--) {

    6.   let p1 = performance.now();

    7.   n = 10000;

    8.   while (n--) { o[Math.random()] = Math.random(); }

    9.   let p2 = performance.now();

    10.   n = 10000;

    11.   while (n--) { m.set(Math.random(), Math.random()); }

    12.   let p3 = performance.now();

    13.   console.log(`Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`);

    14. }

    15. // 内存

    16. class Test {}

    17. let test = new Test();

    18. test.o = o;

    19. test.m = m;

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

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

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

    读取元素时的性能

    测试用的代码如下

     
    1. let n;

    2. let o = {}, m = new Map();

    3.  
    4. n = 10000;

    5. while (n--) { o[Math.random()] = Math.random(); }

    6. n = 10000;

    7. while (n--) { m.set(Math.random(), Math.random()); }

    8.  
    9. let p1 = performance.now();

    10. for (key in o) { let k = o[key]; }

    11. let p2 = performance.now();

    12. for ([key] of m) { let k = m.get(key); }

    13. let p3 = performance.now();

    14. `Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`

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

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

    删除元素时的性能

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

    测试用的代码如下

     
    1. let n;

    2. let o = {}, m = new Map();

    3.  
    4. n = 10000;

    5. while (n--) { o[Math.random()] = Math.random(); }

    6. n = 10000;

    7. while (n--) { m.set(Math.random(), Math.random()); }

    8.  
    9. let p1 = performance.now();

    10. for (key in o) { delete o[key]; }

    11. let p2 = performance.now();

    12. for ([key] of m) { m.delete(key); }

    13. let p3 = performance.now();

    14. `Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`

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

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

    特殊情况

    其实除了最基本的情况之外,还有一种特殊的情况。还记得我们在前面提到的 Object 中键的排序吗?我们提到了其中的非负整数会被最先列出。其实对于非负整数作为键的值和其余类型作为键的值来说,v8 是会对它们进行区别对待的。负整数作为键的部分会被当成数组对待,即非负整数具有一定的连续性时,会被当成快数组,而过于稀疏时会被当成慢数组。

    对于快数组,它拥有连续的内存,所以在进行读写时会更快,且占用更少的内存。更多的内容可以看一下这: 探究JS V8引擎下的“数组”底层实现

    在键为连续非负整数时,性能如下:


    我们可以看到 Object 不仅平均速度更快了,其占用的内存也大大减少了。

    总结

    通过对比我们可以发现,Map 和 Object 各有千秋,对于不同的情况下,我们应当作出不同的选择。所以我总结了一下我认为使用 Map 和 Object 更为合适的时机。

    使用 Map

    • 储存的键不是字符串/数字/或者 Symbol 时,选择 Map,因为 Object 并不支持

    • 储存大量的数据时,选择 Map,因为它占用的内存更小

    • 需要进行许多新增/删除元素的操作时,选择 Map,因为速度更快

    • 需要保持插入时的顺序的话,选择 Map,因为 Object 会改变排序

    • 需要迭代/遍历的话,选择 Map,因为它默认是可迭代对象,迭代更为便捷

    使用 Object

    • 只是简单的数据结构时,选择 Object,因为它在数据少的时候占用内存更少,且新建时更为高效

    • 需要用到 JSON 进行文件传输时,选择 Object,因为 JSON 不默认支持 Map

    • 需要对多个键值进行运算时,选择 Object,因为句法更为简洁

    • 需要覆盖原型上的键时,选择 Object

    虽然 Map 在很多情况下会比 Object 更为高效,不过 Object 永远是 JS 中最基本的引用类型,它的作用也不仅仅是为了储存键值对。

    最后

    1. 感谢阅读,欢迎分享给身边的朋友,

    2. 记得关注噢,黑叔带你飞!

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
js中的Map对象和Object有一些区别。首先,它们的构造方式不同。Object可以通过字面量或构造函数来创建,而Map只能通过构造函数来创建。\[1\]\[2\] 其次,Map对象在特定条件下可以成为更佳的选择。Map对象可以更好地处理键-值对的设置、获取和删除操作,并且可以根据键获取对应的值。而Object在处理少量键-值对时可能更快,特别是当Object被当作数组使用时,浏览器引擎可以进行优化,使用更高效的布局。\[1\]\[3\] 此外,从大型ObjectMap中查找键-值对的性能差异极小。但是,如果代码涉及大量查找操作,有时候选择Object可能更好一些。\[3\] 综上所述,Map对象和Object在使用方式和性能方面有一些区别,开发者可以根据具体的需求选择适合的数据结构。 #### 引用[.reference_title] - *1* *3* [javascript map与原生object的区别](https://blog.csdn.net/u012174809/article/details/124253756)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [jsObjectMap的区别](https://blog.csdn.net/aka_xyz/article/details/127058612)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值