为什么在 JS 中使用 Maps 而不是 Objects?
在 JavaScript 中,对象是一个独立的实体,具有属性和类型。
举个例子,把它和杯子做个比较。杯子是一个物体,有属性。杯子有颜色、款式、重量、材质等等。
目录
1.我在处理对象时遇到的问题:
-
仅可使用字符串或符号作为键。
- 对象有一个限制,即它们的键必须是字符串
const names = { 1: 'One', 2: 'Two', }; Object.keys(names); // => ['1', '2']
- 数字
1
和2
是 names 对象中的键。稍后,当访问该对象的键时,结果发现numbers were converted to strings
。 - 隐式转换键是比较棘手的,因为你
lose the consistency of the types.
-
没有适当的辅助方法来处理对象。
- 为了找到对象的长度,我们需要使用
Object.keys()
或,然后通过访问返回的数组Object.values()
来找到长度。.length
- 类似地,为了对其进行迭代,我们必须使用上面相同的方法对对象执行迭代。
- 为了找到对象的长度,我们需要使用
-
自己的对象属性可能会与从原型继承的属性键发生冲突(例如 toString、构造函数等)。
- 任何对象都会从其原型对象继承属性。
- 从原型继承的属性被意外覆盖是危险的。我们来研究一下这种危险的情况。
- 让我们覆盖对象参与者中的 toString() 属性:
const actor = { name: 'Harrison Ford', toString: 'Actor: Harrison Ford' /* this will cause a problem since we are overriding the toString method of the prototype chain */ };
-
删除键会导致大对象出现问题。
- 在许多情况下,使用删除会导致各种形式和程度的减速,因为它往往会使事情变得更加复杂,迫使引擎(任何引擎)执行更多检查和/或脱离各种快速路径。
2.解决方案:使用Maps数据结构
Map 是键控数据项的集合,类似于对象。但主要区别在于 Map 允许任意类型的键。
方法和属性包括:
new Map()
– 创建地图。map.set(key, value)
– 按键存储值。map.get(key)
– 根据键返回值,如果键在映射中不存在则返回未定义。map.has(key)
– 如果键存在则返回 true,否则返回 false。map.delete(key)
– 按键删除值。map.clear()
– 从地图上删除所有内容。map.size
– 返回当前元素数。
代码:
let map = new Map();
map.set('1', 'str1'); // a string key
map.set(1, 'num1'); // a numeric key
map.set(true, 'bool1'); // a boolean key
// remember the regular object? it would convert keys to string
// Map keeps the type, so these two are different:
alert( map.get(1) ); // 'num1'
alert( map.get('1') ); // 'str1'
alert( map.size ); // 3
地图具有实用且直观的辅助方法,可用于执行不同的操作。
3. 比较:对象和地图
参数 | 目的 | 地图 |
---|---|---|
迭代 | 对象没有实现迭代协议,因此对象不能使用 JavaScript for...of 语句直接迭代(默认情况下)。 | Map 是可迭代的,因此可以直接迭代 |
密钥类型 | 对象的键必须是字符串或符号。 | Map 的键可以是任何值(包括函数、对象或任何原始值)。 |
尺寸 | 必须手动确定对象中的项目数量。 | 可以通过 Map 的 size 属性轻松检索其中的项目数量。 |
表现 | 没有针对频繁添加和删除键值对进行优化。 | 在频繁添加和删除键值对的场景下表现更佳。 |
4. 实例
让我们举一个实现全选功能的例子。
const selectedItems = {};
// we will use object here for quick lookup since its search is O(1)
// adding item into selectedItems
selectedItems['image/png'] = true
selectedItems['text/html'] = true
// removing item from selectedItems
selectedItems['application/pdf'] = false
代码看起来很简单,但如果你注意到我们并没有删除这里的键,而是将其设置为 false。
因此,为了将标题选择状态从更改为partial
或complete
反之亦然,我们需要遍历对象并检测假值和真值。
partial
如果我们可以从对象中删除项目,然后检查对象的长度以确定当前状态是或,那将会很容易complete
。
但是在我们的 V8 引擎中,删除存在性能问题,尤其是当我们想要多次删除键时。
Maps 可以解决这个问题。Map 不仅有删除功能,还能返回大小,不像 Object 那样需要先转换成数组,然后再计算其长度。而且不会造成性能瓶颈。
const selectedItems = new Map()
// adding item into selectedItems
selectedItems.set('image/png')
selectedItems.set('text/html')
// removing item from selectedItems
selectedItems.delete('application/pdf')
其中一个解决方案是当我们想要删除所有选定的项目时设置selectionItems{}
,但在某些情况下这不是一个可扩展的解决方案。
当我们在表格中进行分页时,我们会遇到select-all
针对特定于当前页的项目执行分页的情况,而不是针对下一页或上一页的项目执行分页的情况。
在这种情况下,如果我们设置selectedItems = {}
它将重置所有值,这是一个错误的解决方案。
因此,地图是更具可扩展性的解决方案,因为它不会遇到与删除键有关的任何问题。
5. 地图中的问题
-
地图并非用来取代物体
- 如果我们只使用基于字符串的键并且需要最大的读取性能,那么对象可能是一个更好的选择。
- 这是因为 Javascript 引擎在后台将对象编译为 C++ 类,并且属性的访问路径比 Map().get() 的函数调用快得多。
- 添加或删除属性会导致类的形状发生变化,并且需要重新编译支持类,这就是为什么使用对象作为字典进行大量添加和删除操作非常慢,但在不改变对象的情况下读取现有键却非常快的原因。
-
地图不可序列化
- 地图本身不支持序列化或解析
- Redux 不建议使用不可序列化的数据结构,因为它可能会破坏 dev-tools 的工作,并且还会导致无法按预期呈现更新:https://redux.js.org/style-guide/style-guide#do-not-put-non-serializable-values-in-state-or-actions
6. 结论
回顾一下,虽然我们仍然严重依赖 JavaScript 对象来完成保存结构化数据的工作,但它们有一些明显的局限性
这些限制可以通过 Map 来解决。此外,Map 还具有其他优势,例如可以作为迭代器,并且可以轻松查找大小。
对象不适合存储持续更新、循环、修改或排序的信息。在这种情况下,请使用 Map。
总而言之,使用 map 时要有目的性。将 map 和对象视为 let 和 const 变量的类似用法。
文章来源:https://dev.to/faisalpathan/why-to-use-map-over-object-in-js-306m