广告
返回顶部
首页 > 资讯 > 前端开发 > JavaScript >JavaScript中Map与Object应用场景
  • 171
分享到

JavaScript中Map与Object应用场景

2024-04-02 19:04:59 171人浏览 薄情痞子
摘要

目录引言为什么 Object 缺少哈希映射不需要的继承名称冲突尺寸迭代清除检查属性是否存在哈希映射的映射性能基准测试实施细节字符串键整数键数字键内存使用情况结论浏览器兼容性注意事项引

引言

javascript 中选择 Object 和 Map 的缺失指南

在 JavaScript 中,对象很方便。它们使我们能够轻松地将多条数据组合在一起。在 es6 之后,我们为该语言添加了一个新功能 - Map. 在很多方面,它似乎比Object更强大,但界面有点笨拙。然而,大多数人在需要哈希映射时仍然会使用对象,并且只有在他们意识到键不能只是他们用例的字符串时才切换到使用。因此,在当今的 JavaScript 社区中Map仍未得到充分利用。

在这篇文章中,我将分解您应该考虑使用Mapmore 的所有原因及其性能特征与基准测试。

在 JavaScript 中,Object 是一个相当宽泛的术语。几乎所有东西都可以是一个对象,除了两种底部类型 -nullundefined. 在这篇博文中,Object 仅指普通的旧对象,由左大括号{和右大括号分隔}

博士

  • 用于Object在作者时已知的属性/字段数量固定且有限的记录,例如配置对象。以及一般一次性使用的任何东西。
  • 用于Map字典或哈希映射,其中条目数量可变,更新频繁,其键在作者时可能未知,例如事件发射器。
  • 根据我的基准测试,除非键是小整数字符串,否则在插入、删除和迭代速度Map方面确实比Object高效,并且它消耗的内存Object相同大小的要少。

为什么 Object 缺少哈希映射

将对象用于哈希映射最明显的缺点可能是对象只允许字符串和符号作为键。任何其他类型都将通过toString将这些方法隐式转换为字符串。

const foo = []
const bar = {}
const obj = {[foo]: 'foo', [bar]: 'bar'}
console.log(obj) // {"": 'foo', [object Object]: 'bar'}

更重要的是,将对象用于哈希映射可能会导致混淆和安全隐患。

不需要的继承

在 ES6 之前,获取哈希映射的唯一方法是创建一个空对象。

const HashMap = {}

但是,在创建后,此对象不再为空。虽然hashMap是用一个空的对象字面量制作的,但它会自动继承自Object.prototype. 这就是为什么我们可以调用像hasOwnPropertytoStringconstructoron这样的方法,hashMap即使我们从未在对象上明确定义这些方法。

由于原型继承,我们现在混合了两种类型的属性:存在于对象本身中的属性,即它自己的属性,以及存在于原型链中的属性,即继承的属性。因此,我们需要额外的检查(例如hasOwnProperty)来确保给定的属性确实是用户提供的,而不是从原型继承的。

最重要的是,由于属性解析机制在 JavaScript 中的工作方式 Object.prototype,运行时的任何更改都会在所有对象中产生连反应。 这为原型污染攻击打开了大门,这对于大型 JavaScript 应用程序来说可能是一个严重的安全问题。

幸运的是,我们可以通过使用来解决这个问题Object.create(null),这会生成一个不继承任何内容的对象Object.prototype

名称冲突

当一个对象自己的属性与其原型上的属性发生名称冲突时,它会破坏预期并因此使您的程序崩溃。

例如,我们有一个foo接受对象的函数:

function foo(obj) {
	//...
	for(const key in obj) {
		if(obj.hasOwnProperty(key)) {
		}
	}
}

存在可靠性风险obj.hasOwnProperty(key):考虑到属性解析机制在 JavaScript 中的工作方式,如果obj包含用户提供的同名属性,则会hasOwnProperty隐藏Object.prototype.hasOwnProperty. 结果,我们不知道在运行时会准确调用哪个方法。

可以进行一些防御性编程来防止这种情况。例如,我们可以借用hasOwnProperty来代替: Object.prototype

function foo(obj) {
	//...
	for(const key in obj) {
		if(Object.prototype.hasOwnProperty.call(obj, key)) {
			// ...
		}
	}
}

一种更短的方法可能是在对象文字上调用该方法,{}.hasOwnProperty.call(key)但它仍然很麻烦。这就是为什么有一个新添加的静态方法Object.hasOwn

尺寸

Object没有提供方便的 api 来获取大小,即属性的数量。构成对象大小的因素也有细微差别:

  • 如果您只关心字符串、可枚举键,那么您可以将键转换为数组Object.keys()并获取其length.
  • 如果您想考虑不可枚举的字符串键,那么您必须使用Object.getOwnPropertyNames来获取键列表并获取其长度。
  • 如果您对符号键感兴趣,可以使用getOwnPropertySymbols来显示符号键。或者,您可以使用Reflect.ownKeys 同时获取字符串键和符号键,无论它是否可枚举。

迭代

遍历对象也有类似的复杂性。

我们可以使用良好的旧for...in循环。但它揭示了继承的可枚举属性:

Object.prototype.foo = 'bar'
const obj = {id: 1} 
for(const key in obj) {
	console.log(key) // 'id', 'foo'
}

我们不能用for...of与对象一起使用,因为默认情况下它不是可迭代的,除非我们用Symbol.iterator在其上显式定义方法。

我们可以使用Object.keys,Object.valuesObject.entries来获取可枚举的字符串键(或/和值)列表,然后对其进行迭代,这会引入额外的开销步骤。

最后,臭名昭著的插入顺序没有得到充分尊重。在大多数浏览器中,整数键按升序排序并且优先于字符串键,即使字符串键插入到整数键之前也是如此。

const obj = {}
obj.foo = 'first'
obj[2] = 'second'
obj[1] = 'last'
console.log(obj) // {1: 'last', 2: 'second', foo: 'first'}

清除

没有简单的方法可以从对象中删除所有属性,您必须使用delete操作符一个一个地删除每个属性,这在历史上被认为是缓慢的。但是我的基准测试表明,它的性能实际上比不上Map.prototype.delete

检查属性是否存在

最后,我们不能依赖点/括号符号来检查属性是否存在,因为值本身可以设置为undefined. 相反,我们必须使用Object.prototype.hasOwnProperty和 Object.hasOwn

const obj = {a: undefined}
Object.hasOwn(obj, 'a') // true

哈希映射的映射

ES6 带来了 Map。它更适合哈希映射。

首先,与Object只允许字符串和符号作为键不同,它Map支持任何数据类型的键。

但是,如果您Map用于存储对象的元数据,那么您应该使用它WeakMap来避免内存泄漏。

但更重要的是,Map它提供了用户定义和内置程序数据之间的清晰分离,但代价是额外的检索条目。

Map还提供了更好的人体工程学:Map默认情况下,A 是可迭代的。这意味着您可以使用for...of轻松迭代地图,并执行诸如使用嵌套解构从地图中提取第一个条目之类的操作。

const [[firsTKEy, firstValue]] = map

Object相比Map为各种常见任务提供专用 API:

Map.prototype.has检查给定条目的存在,Object.prototype.hasOwnProperty/Object.hasOwn在对象上相比不那么尴尬

Map.prototype.get返回与提供的键关联的值。人们可能会觉得这比对象上的点表示法或括号表示法更笨拙。然而,它在用户数据和内置方法之间提供了清晰的分离。

Map.prototype.size返回 a 中的条目数,Map它显然是获得对象大小所必须执行的操作的赢家。此外,它要快得多。

Map.prototype.clear删除 a 中的所有条目,Map它比运算符delete快得多。

性能

在大多数情况下,JavaScript 社区似乎普遍认为MapObject好. 有些人声称要通过Object切换到Map.

我磨练 LeetCode 的经验似乎证实了这个信念:Leetcode 将大量数据作为测试用例提供给您的解决方案,如果您的解决方案耗时过长,它就会超时。像这样的问题只有在你使用Object时才会超时,而不是在Map.

但是,我相信只是说“Map比对象更快”是简化的。一定有一些细微差别是我想自己找出来的。所以。我构建了一个小应用程序来运行一些基准测试。

基准测试实施细节

该应用程序有一个表格,显示在ObjectMap 上测量的插入、迭代和删除速度。

插入和迭代的性能以每秒操作数来衡量。我编写了一个 util 函数measureFor,它重复运行目标函数,直到达到指定的最小时间阈值(即durationUI 上的输入字段)。它返回每秒执行此类函数的平均次数。

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);
}

至于删除,我只是要测量使用delete运算符从对象中删除所有属性所需的时间,并将其与 Map.prototype.delete相同大小的 Map 的时间进行比较。我可以使用Map.prototype.clear,但它违背了基准测试的目的,因为我确信它会更快。

在这三个操作中,我更加关注插入,因为它往往是我在日常工作中执行的最常见的操作。对于迭代性能,很难提出一个包罗万象的基准,因为我们可以在给定对象上执行许多不同的迭代变体。这里我只测量for ... in循环。

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

  • 字符串,例如yekwl7caqejth7aawelo4.
  • 整数字符串,例如123.
  • 由 生成的数字字符串Math.random().toString(),例如0.4024025689756525

所有的键都是随机生成的,所以我们不会碰到 V8 实现的内联缓存。我还显式地将整数和数字键转换为字符串,然后再将它们添加到对象以避免隐式转换的开销。

最后,在基准测试开始之前,还有一个至少 100 毫秒的预热阶段,我们反复创建新的对象和地图,这些新对象和地图会立即被丢弃。

如果你想玩,我把代码放在Codesandbox上。

我从100个属性/条目的Object和Map开始,一直到5000000,让每种类型的操作持续运行10000ms,看看它们彼此之间的表现如何。以下是我的发现……

为什么我们在条目数达到 5000000 时停止?

字符串键

一般来说,当键是(非数字)字符串时,在所有操作上都Map优于Object

但细微差别在于,当条目数量不是很大(低于 100000)时,Map插入速度是Object插入速度的两倍,但随着大小增长超过 100000,性能差距开始缩小。

我制作了一些图表来更好地说明我的发现。

上图显示了随着条目数量的增加(x 轴),插入率如何下降(y 轴)。但是因为 X 轴扩展得太宽(从 100 到 1000000),所以很难分辨这两条线之间的差距。

然后我使用对数刻度来处理数据并制作下面的图表。

您可以清楚地看出两条线正在汇合。

我制作了另一个图表,绘制了MapObject插入速度相关的速度。您可以看到Map开始时比Object快. 然后随着时间的推移,性能差距开始缩小。Map随着规模增长到 5000000,最终速度仅快 30%。

但是,我们大多数人在一个对象或映射中永远不会有超过 100 万个条目。具有数百或数千个条目的大小.  因此,我们是否应该把它留在那儿,然后全力以赴开始重构我们的代码库Map

绝对不会……或者至少没有期望我们的应用程序会快 2 倍。请记住,我们还没有探索过其他类型的键。让我们看一下整数键。

整数键

我特别想对具有整数键的对象运行基准测试的原因是 V8 在内部优化了整数索引属性并将它们存储在可以线性和连续访问的单独数组中。我找不到任何资源来确认它对Map 采用了相同类型的优化。

让我们首先尝试 [0, 1000] 范围内的整数键。

正如我所料,这次Object 跑赢大盘。  Object插入速度比Map快 65%,迭代速度快 16%。

让我们扩大范围,使键中的最大整数为 1200。

现在似乎Map开始比Object快一点

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

当属性大小为1000时,Object的插入速度比Object快70%,迭代速度比Map慢2倍。

我尝试了许多不同的Object/Object大小和整数键范围的组合,但未能想出一个明确的模式。但我看到的总体趋势是,随着大小的增长,使用一些相对较小的整数作为键,Object在插入方面的性能可以比map更好,总是与删除大致相同,迭代速度是map的4到5倍。最大整数键的阈值,即Object在插入时开始变慢的阈值,将随着Object的大小而增长。例如,当该Object只有100个表项时,阈值为1200;当它有10000个条目时,阈值似乎在24000左右。

数字键

最后,我们来看看最后一种键——数字键。

从技术上讲,以前的整数键也是数字的。这里的数字键特指生成的数字字符串Math.random().toString()

结果与字符串-键的情况类似:map开始时比Object快得多(插入和删除快2倍,迭代快4-5倍),但随着大小的增加,增量越来越小。

嵌套对象/地图呢?

内存使用情况

基准测试的另一个重要方面是内存利用率。

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

我创建了一个小脚本来测量它们各自的内存使用情况,并在每次测量中手动触发完全垃圾收集。运行它,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它不存储属性描述符,例如writable// like 。enumerable``configurable``Object

结论

那么我们能从这一切中得到什么?

  •  MapObject更快,除非您有小的整数和数组索引键,而且它的内存效率更高。
  • 如果需要经常更新的哈希映射,可以使用Map; 如果你想要一个固定的键值集合(例如记录),请使用Object,并注意原型继承带来的陷阱。

如果您确切了解 V8 如何优化的细节,Map或者只是想指出我的基准测试中的缺陷,请联系我。我很乐意根据您的信息更新这篇文章!

浏览器兼容性注意事项

Map是 ES6 的一个特性。到目前为止,我们大多数人都不应该担心它的兼容性,除非你的目标用户群是一些小众的旧浏览器。“旧”是指比 IE 11 更早,因为即使 IE 11 也支持Map而此时 IE 11已死。我们不应该在默认情况下盲目地转译和添加 polyfill 到目标 ES5,因为它不仅会膨胀你的包大小,而且与现代 JavaScript 相比运行起来很慢。最重要的是,它会惩罚 99.999% 的使用现代浏览器的用户。

另外,我们不必放弃对旧版浏览器的支持——nomodule通过提供后备包来提供旧版代码,这样我们就可以避免使用现代浏览器降低访问者的体验。

JavaScript 语言在不断发展,平台在优化现代 JavaScript 方面也越来越好。我们不应该以浏览器兼容性为借口忽略所有已做出的改进。

原文翻译https://www.zhenghao.io/posts/object-vs-map

以上就是JavaScript中Map与Object应用场景的详细内容,更多关于JavaScript中Map Object的资料请关注编程网其它相关文章!

--结束END--

本文标题: JavaScript中Map与Object应用场景

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

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

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

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

下载Word文档
猜你喜欢
  • JavaScript中Map与Object应用场景
    目录引言为什么 Object 缺少哈希映射不需要的继承名称冲突尺寸迭代清除检查属性是否存在哈希映射的映射性能基准测试实施细节字符串键整数键数字键内存使用情况结论浏览器兼容性注意事项引...
    99+
    2022-11-13
  • JavaScript中forEach和map的使用场景
    目录foeEachforEach方法是同步执行的,不能使用异步操作。mapmap方法不会遍历已经添加并且未被删除的元素。什么是迭代器模式JavaScript 的 forEach 和 ...
    99+
    2023-05-18
    JavaScript forEach map  forEach map
  • JavaScript的应用场景有哪些
    本篇内容主要讲解“JavaScript的应用场景有哪些”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“JavaScript的应用场景有哪些”吧!   JavaS...
    99+
    2022-10-19
  • Clouda框架介绍与应用场景
    Clouda是一个基于浏览器运行的前端应用开发框架。它提供了一套完整的开发工具和资源,可以帮助开发者快速构建跨平台的Web应用。Cl...
    99+
    2023-08-23
    Clouda
  • Javascript闭包的作用和应用场景
    本篇内容介绍了“Javascript闭包的作用和应用场景”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、...
    99+
    2022-10-19
  • Node.js中子进程与应用场景有哪些
    这篇文章主要介绍了Node.js中子进程与应用场景有哪些,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。背景由于ons基于 C艹 封装而来,不...
    99+
    2022-10-19
  • TypeScript中枚举类型的理解与应用场景
    目录一、是什么二、使用数字枚举字符串枚举异构枚举本质三、应用场景总结一、是什么 枚举是一个被命名的整型常数的集合,用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为...
    99+
    2022-11-12
  • Kafka及场景应用(中3)
    版权声明:欢迎转载,但是看在我辛勤劳动的份上,请注明来源:http://blog.csdn.net/yinwenjie(未经允许严禁用于商业用途!)                https://blog.csdn.net/yinwenj...
    99+
    2023-01-31
    场景 Kafka
  • PHP设计模式大全与应用场景
    面向对象编程的基本原则: 单一职责:一个类,只需要做好一件事情。 开放封闭:一个类,应该是可扩展的,而不可修改的。 依赖倒置:一个类,不应该强依赖另外一个类。每个类对于另外一个类都是可替换的。 配置化:尽可能的使用配置,而不是硬...
    99+
    2023-10-26
    设计模式 PHP设计模式
  • JavaScript尾递归的实现及应用场景
    目录什么是尾递归和递归的差别尾递归的优化应用场景总结什么是尾递归 尾递归是一种特殊的递归,它的特点是在函数的最后一步调用自身,而不是在调用后还有其他操作。尾递归可以有效地避免栈溢出的...
    99+
    2023-05-18
    Javascript尾递归
  • WebSocket在Web应用中的应用场景
    WebSocket是一种在现代Web浏览器和服务器之间进行双向通信的协议。与传统的HTTP协议不同,WebSocket允许服务器主动发送数据到客户端,而不需要客户端主动发起请求。这种实时双向通信的特性使得WebSocket在多种Web应用场...
    99+
    2023-10-21
    实时通信 数据推送 多人协作
  • JavaScript教程中的重定向有哪些应用场景?
    重定向是Web开发中常用的一种技术,它可以将用户请求的页面重定向到另一个页面或者网站上。JavaScript是一门流行的脚本语言,用于Web开发中的客户端脚本编程。在JavaScript教程中,重定向也是一种重要的技术,本文将介绍Java...
    99+
    2023-06-26
    重定向 javascript 教程
  • Python 面试中,javascript 函数的应用场景有哪些?
    Python 和 JavaScript 都是非常流行的编程语言,在现代软件开发中都扮演着重要的角色。当这两种语言交汇在一起时,我们就需要考虑它们之间的关系以及如何在面试中谈论它们。特别是在 Python 面试中,经常会涉及到 JavaSc...
    99+
    2023-08-22
    面试 javascript 函数
  • 队列的作用和在PHP与MySQL中的应用场景
    队列是计算机科学中非常重要的一种数据结构,它可以帮助我们实现任务的异步处理和解耦。队列的基本原则是“先进先出”,即先放入队列的任务会被先取出来处理。队列的作用:异步处理:当一个任务需要耗费很长时间来完成时,可以把任务放入队列,然后让程序继续...
    99+
    2023-10-21
    PHP 队列 MySQL。
  • JavaScript中Reduce10个常用场景技巧
    目录累加/累积求最大/最小值格式化搜索参数反序列化搜索参数拉平嵌套数组实现 flat数组去重数组计数获取对象多个属性反转字符串不知道大家平常用 Reduce 多不多,反正本瓜用的不多...
    99+
    2022-11-13
  • Redis中有哪些应用场景
    本篇内容介绍了“Redis中有哪些应用场景”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!Redis数据类型...
    99+
    2022-10-19
  • JavaScript在Linux和Unix系统中的应用场景有哪些?
    JavaScript是一种广泛应用于Web开发的脚本语言,但在Linux和Unix系统中,JavaScript也有着广泛的应用场景。在本文中,我们将探讨JavaScript在Linux和Unix系统中的应用场景,并给出一些演示代码。 N...
    99+
    2023-07-04
    linux unix javascript
  • Vue中插槽slot的使用方法与应用场景详析
    什么是插槽? 我们知道在Vue中 Child 组件的标签 的中间是不可以包着什么的 。 可是往往在很多时候我们在使用组件的时候总想在组件间外面自定义一些标签,vue新增了一种插槽...
    99+
    2022-11-12
  • Vue3中reactive与ref函数使用场景
    目录前言简单了解 ref & reactivereactiverefreactive 能做的 ref 也能做,并且还是用 reactive 做的ref 能做,但是 react...
    99+
    2022-11-13
  • JavaScript中this的使用场景有哪些
    本篇文章给大家分享的是有关JavaScript中this的使用场景有哪些,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。JavaScript是什么JavaScript是一种直译式...
    99+
    2023-06-14
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作