iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >JavaScript 中什么时候使用 Map 更好
  • 226
分享到

JavaScript 中什么时候使用 Map 更好

2024-04-02 19:04:59 226人浏览 独家记忆
摘要

目录Map 用作 Hash Map性能基准测试的实现细节字符串类型的键整数类型的键数值类型的键内存使用总结浏览器兼容性笔记Map 用作 Hash Map es6 给我们带来了&nbs

Map 用作 Hash Map

es6 给我们带来了 Map,它更适合当做 hash map 的用例。

首先,它并不像 Object 那样只允许 key 为 string 和 symobol,Map 的 key 支持任何数据类型。

可是如果你使用 Map 为对象存储元数据,应该使用 WeakMap 取而代之以此避免内存泄漏。

更重要的是,Map 为用户自定义和内置对象属性提供了清晰的界限,使用一个额外的方法 Map.prototype.get 获取条目。

Map 同样也提供了更人性化的方法,Map 默认就可迭代,意味着你可以轻松的与 for...of 一起使用,同样使用嵌套的解构获取第一个条目。

const [[firsTKEy, firstValue]] = map

与 Object 对比,Map 为各种常见任务提供了具体的方法:

  • Map.prototype.has 检查一个给定条目的存在,与对象上的 Object.prototype.hasOwnProperty / Object.hasOwn 对比还是方便许多。
  • Map.prototype.get 返回与提供的 key 相关的值。可能会有人觉得比对象上的 . 和 [] 稍显笨重。然后它为用户自定义的数据和内置的方法提供了清晰的界限。
  • Map.prototype.clear 可以清除 Map 上的所有条目且比 delete 操作更快。

性能

javascript 社区中似乎有一种共同的认知:Map 比 Object 快,在多数情况下,有些人在从 Object 切换到 Map 时看到了性能的提升。

从我在磨人的 LeetCode 中刷题的经验中似乎更加确认了这个观点:LeetCode 使用了大量的数据来对你的解决方案做测试用例,若你的答案花费了太长时间则会超时。像这种问题一般在你使用 Object 时出现几次,而 Map 则没有。

可是,我相信只简单的说 Map 比 Object 快很笼统,肯定有一些细微的差别需要我去发现。因此,我创建了一个小应用来运行一些基准测试。

基准测试的实现细节

这个应用有一个表格用来展示分别对 Object 和 Map 作用插入、迭代和删除的速度。

插入和迭代的性能是以每秒来测量的,我写了一个工具方法 measureFor 用来重复执行目标函数,直到设定的最小阈值(就是传入的 duration 值)。它返回的是每秒函数执行的平均次数。

function measureFor(f, duration) {
  let iterations = 0;
  const now = perfORMance.now();
  let elapsed = 0;
  while (elapsed < duration) {
    f();
    elapsed = performance.now() - now;
    iterations++;
  }

  return ((iterations / elapsed) * 1000).toFixed(4);
}

对于删除,我打算简单的对比一下从长度相同的 Object 、 Map 分别使用 delete 和 Map.protoype.delete 移除所有属性所花费时间的对比。我知道有 Map.protype.clear 但据我所知它非常的快,这有悖于设置基础测试的目的。

在这三种操作中,因为每天的工作中经常使用到插入操作,所以我更关心它。对于迭代的性能,因为有很多方法用于对象的迭代,所以很难包含所有的基准测试。我在这里只测试了 for ... in 循环。

我这里使用了三种类型的 key:

  • 字符串,例如:'yekwl7caqejth7aawelo4'
  • 整数字符串,例如:123
  • 通过 Math.random().toString() 生产的数字字符串,例如:'0.6514674912156457'

所有的 key 都是随机生成的,所以不会触发 V8 内部实现的缓存机制。在把属性添加到对象上之前,我还在显性的把整数和数值类型的 key 通过 toString 转换为字符串以此避免隐式开销。

最后,在基准测试开始之前,还有一个 100ms 的热身阶段,就是重复创建新的 object 和 map 并立即丢弃掉所耗费的时间。

代码放到了 CodeSandbox 如果你想试玩一下。

从 100 个属性/条目大小的 Object 和 Map 开始,一直到5000000,每种类型的操作都执行了 10000ms 来对比它们之间的表现,

如下:

为何把条目数达到 5000000时才停止? 这是 JavaScript 中能得到的最大对象,根据 StackOverflow 上一名活跃的 V8 工程师 @jmrk 所说:"如果 key 为字符串,当一个普通的对象元素达到 8.3M 时会各种对它的操作会变得非常慢(这是有技术原因的:某个位域有23位宽,当超过时采取非常缓慢的回退路径。)"

字符串类型的键

通常来说,当 key 为字符串(非数值型),在各种操作中 Map 的表现胜过 Object

但是当条目数并没有特别大的时候(100000 以下的时候),在插入操作的速度上 Map 基本是 Object 的两倍,但当大小超过 100000 时,性能差距会开始缩小。

我制作了一个图表来说明我的发现:

上面的图表演示了插入速率随着条目数(x-axis)的增加是如何下降的(y-axis)。可是,因为 x-axis 扩展的越来越大(从 100 到 1000000),很难区分出两条线之间的间隔差距。

然后我用对数比例来处理数据,做出了下面的图表:

你会清楚的分不出两条线渐渐地重叠。

我又用另一个图表来展示当插入操纵时 Map 比 Object 快多少。你可以看到期初 Map 比 Object 快 2倍,随着时间的推移性能差距开始缩小。最终,当大小达到 5000000 时,Map 只快了 30%。

我们的 object 和 map 的条目永远不可能多余 1百万。成百上千的条目,Map 至少比 Object 快 2 倍。因此,我们是否应该开始使用 Map 来重构我们的代码?

当然不是,至少不能期望我们的程序变得比之前快 2倍。记住我们还没有探索其它类型的 key,接下来让我们一起看看整数类型的 key。

整数类型的键

我特别想运行对象上键为整数类型的原因是 V8 内部为整数索引的属性做了优化以及把它们存储在一个分开的数组中,然后可以线性和连续的获取。可是我没有找到任何资料来证明 V8 对 Map 做了同样的优化。

我们先在 [0,1000] 之间尝试整数类型的 key。

就像我预期的一样,这次 Object 比 Map 做得好,在插入方面快 65%以及循环方面快 16%。

让我们把范围调整到最大 1200。

现在看起来 Map 在插入方面快一些以及循环方便快 5 倍。

现在我们仅仅增加了键的范围,而不是 Object 和 Map 的实际大小。让我们来增加它们的大小看看如何影响性能。

当大小为 1000 时,插入方面 Object 比 Map 快 70% 以及循环方面快 2 倍。

我尝试了很多种 Object / Map 大小与整数键范围的组合但没有得到一个清晰的模式。但我看到的一般趋势是,随着大小的增长,以一些相对较小的整数为键,对象在插入方面的性能比 Map 更强,在删除方面总是大致相同,迭代速度要慢 4 或 5 倍。对象在插入时开始变慢的最大整数键的阈值会随着对象的大小而增长。例如,当对象只有 100 个条目时,阈值是 1200 ;当它有 10000 个条目时,阈值似乎是 24000 左右。

数值类型的键

最后,我们一起看一看最后一种类型的键--数值型。

从技术上来说,前面的整数类型的键也是数值型,不过这里的数值型特指通过 Math.random().toString() 生成的数值字符串。

结果有点类似字符串类型的键:Map 开始时比 Object 快得多(插入和删除快2倍,迭代快4-5倍),但随着我们规模的增加,这个差距也越来越小。

嵌套的 Object / Map 呢? 你可能已经发现了我只讲了深度只有一层的 Object 和 Map。我确实增加了一些嵌套深度但是我发现只要总条目数相同性能特性大体一致,不管嵌套了多少层。

例如,我们有一个宽度为 100 和深度为 3 总数为 100 万的条目,结果与宽度为 1000000 深度为 1 的性能几乎相同。

内存使用

基准测试的另一个重要因素为内存利用。

由于我无法控制浏览器环境中的垃圾收集器,我决定在node中运行基准测试。

我创建了一个小脚本来测量在每个测试中通过手动触发完全垃圾回收时各自的内存使用情况。

与 node --expose-gc 一起运行将会得到如下结果:

{
  object: {
    'string-key': {
      '10000': 3.390625,
      '50000': 19.765625,
      '100000': 16.265625,
      '500000': 71.265625,
      '1000000': 142.015625
    },
    'numeric-key': {
      '10000': 1.65625,
      '50000': 8.265625,
      '100000': 16.765625,
      '500000': 72.265625,
      '1000000': 143.515625
    },
    'integer-key': {
      '10000': 0.25,
      '50000': 2.828125,
      '100000': 4.90625,
      '500000': 25.734375,
      '1000000': 59.203125
    }
  },
  map: {
    'string-key': {
      '10000': 1.703125,
      '50000': 6.765625,
      '100000': 14.015625,
      '500000': 61.765625,
      '1000000': 122.015625
    },
    'numeric-key': {
      '10000': 0.703125,
      '50000': 3.765625,
      '100000': 7.265625,
      '500000': 33.265625,
      '1000000': 67.015625
    },
    'integer-key': {
      '10000': 0.484375,
      '50000': 1.890625,
      '100000': 3.765625,
      '500000': 22.515625,
      '1000000': 43.515625
    }
  }
}

很明显,Map 比 Object 消耗的内存少20%到50%不等,结果并不意外因为 Map 不存储属性的描述类似 Object 上的 writable 、enumerable 、configurable

总结

那么,我们从其中能得到什么?

  • Map 比 Object 更快,除非你有小的整数、数组索引为键,而且它更节省内存。
  • 若 Hash Map 需要经常更新你应该使用 Map;若你的集合为固定的键值(例如:记录)则使用 Object,但是请注意原型继承带来的陷阱。

如果你知道 V8 优化 Map 的细节,或者只是想指出我的基准测试中的缺陷,请与我联系。我很乐意根据你的信息来更新这个帖子。

浏览器兼容性笔记

Map 是 ES6 引入的特性,目前为止我们不需要担心其兼容性除非你的客户使用的旧浏览器。旧指比 IE11 版本低,即使 IE11 支持 Map 但它已经  了。默认情况下,我们不需要费尽心思的添加转换器和垫片来支持 ES5 ,因为那样会增加你的打包体积,同样与现代浏览器比更慢。最重要的是,那样会对 99.999% 使用现代浏览器的用户带来负担。

另外,我们不必放弃对传统浏览器的支持--通过 nomodule 提供回退包来服务传统代码,这样我们就可以避免降低使用现代浏览器的访问者的体验。如果你需要更多的说服力,请参考《过渡到现代JavaScript》。

JavaScript语言在不断发展,平台在优化现代JavaScript方面也不断完善。我们不应该以浏览器兼容性为借口,忽视所有已经取得的改进。

到此这篇关于JavaScript 中什么时候使用 Map 更好的文章就介绍到这了,更多相关JavaScript 使用 Map 内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

--结束END--

本文标题: JavaScript 中什么时候使用 Map 更好

本文链接: https://www.lsjlt.com/news/165864.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • JavaScript 中什么时候使用 Map 更好
    目录Map 用作 Hash Map性能基准测试的实现细节字符串类型的键整数类型的键数值类型的键内存使用总结浏览器兼容性笔记Map 用作 Hash Map ES6 给我们带来了&nbs...
    99+
    2024-04-02
  • 在什么时候适合使用Map
    本篇内容介绍了“在什么时候适合使用Map”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!「Map」映射是一种...
    99+
    2024-04-02
  • JavaScript 什么时候使用回调
    JavaScript 什么时候使用回调,很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。 JavaScript 何时使用回调? ...
    99+
    2024-04-02
  • c++中endl什么时候使用
    std::endl 用于将换行符写入流,通常在需要显式结束行时使用。它强制刷新流并避免缓冲行为。替代方法包括直接写入 '\n' 字符或使用 std::flush 手动刷新流。 什么时候...
    99+
    2024-04-28
    c++
  • 什么时候使用flags
    这篇文章主要讲解了“什么时候使用flags”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“什么时候使用flags”吧!   Possible flags:(...
    99+
    2024-04-02
  • javascript是什么时候出的
    这篇文章给大家分享的是有关javascript是什么时候出的的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。 javascript是1995年出的;Ja...
    99+
    2024-04-02
  • HTML div什么时候使用
    本篇内容主要讲解“HTML div什么时候使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“HTML div什么时候使用”吧! 1、div:作为布局以及样式化...
    99+
    2024-04-02
  • es6中promise什么时候用
    本教程操作环境:windows7系统、ECMAScript 6版、Dell G3电脑。Promise的含义Promise是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6将其写进了...
    99+
    2022-11-22
    javascript promise ES6
  • 什么时候使用Lambda函数?
    原文来自:1前言Python 中定义函数有两种方法,一种是用常规方式 def 定义,函数要指定名字,第二种是用 lambda 定义,不需要指定名字,称为 Lambda 函数。Lambda 函数又称匿名函数,匿名函数就是没有名字的函数,函数没...
    99+
    2023-06-02
  • JavaScript中怎么更好地使用数组
    这篇文章给大家分享的是有关JavaScript中怎么更好地使用数组的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。使用 Array.includes 替代 Array.indexOf"如果需要在数组中查找...
    99+
    2023-06-15
  • CSS中什么时候使用内部样式表
    这篇文章主要介绍“CSS中什么时候使用内部样式表”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“CSS中什么时候使用内部样式表”文章能帮助大家解决问题。 当单个文档...
    99+
    2024-04-02
  • Windows XP什么时候将不再更新
    这篇文章主要介绍了Windows XP什么时候将不再更新,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。按照微软计划,从明年4月8日开始,用户依然可以正常使用Windows X...
    99+
    2023-06-26
  • JavaScript中map()方法有什么用
    这篇文章将为大家详细讲解有关JavaScript中map()方法有什么用,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。map()为每个数组元素调用函数的结果来创建新数组。...
    99+
    2024-04-02
  • 什么时候应该使用 golang 函数?
    何时使用 go 函数?需要将代码分解成较小的块。需要重复使用代码。需要将代码逻辑封装到一个可重用的模块中。 何时使用 Go 函数 Go 中的函数是一种封装代码并将代码块组织在一起的方法...
    99+
    2024-04-25
    函数 golang
  • Java消息队列什么时候使用
    本篇内容主要讲解“Java消息队列什么时候使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Java消息队列什么时候使用”吧!何时需要消息队列当你需要使用消息队列时,首先需要考虑它的必要性。可以...
    99+
    2023-06-04
  • HTML中什么时候使用子元素选择器
    这篇文章主要介绍“HTML中什么时候使用子元素选择器”,在日常操作中,相信很多人在HTML中什么时候使用子元素选择器问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”HTML中什...
    99+
    2024-04-02
  • 使用Mybatis更新时候只更新变更部分的方法
    目录Mybatis更新时候只更新变更部分具体可以参考以下代码Mybatis update更新字段的使用多个mapper方法,更新单字段通用mapper方法,java代码控制...
    99+
    2024-04-02
  • mysql什么时候可以使用索引
    这篇文章主要介绍mysql什么时候可以使用索引,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!            &nb...
    99+
    2024-04-02
  • 什么时候需要使用HTTPS代理
    本篇文章给大家分享的是有关什么时候需要使用HTTPS代理,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。HTTPS是HTTP的安全版本,它在HTTP上建立SSL加密层并加密传输的...
    99+
    2023-06-25
  • js中什么时候不能使用箭头函数
    目录箭头函数箭头函数有什么缺点?什么时候不能使用箭头函数?1. 对象方法中,不适用箭头函数为什么对象方法中,箭头函数的this指向不是这个对象?2. 原型方法中,不适用箭头函数3. ...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作