对象在JavaScript中很方便。它们使我们能够轻松地将多个数据块组合在一起。ES6之后,又有了新的语言补充——Map。在许多方面,它看起来像是一个功能更强大的对象,但界面有点笨拙。然而,大多数开发人员在需要哈希映射时仍会使用对象,只有在意识到键和值不能只是字符串时才转而使用Map。因此,Maps在今天的JavaScript社区中仍然没有得到充分利用。在本文中,我将列出一些为什么应该更多地考虑Map的原因。为什么对象不适合HashMap的使用在HashMap中使用对象最明显的缺点是对象只允许键为字符串和符号。任何其他类型的键都通过toString方法隐式转换为字符串。constfoo=[]constbar={}constobj={[foo]:'foo',[bar]:'bar'}console.log(obj)//{"":'foo',[objectObject]:'bar'}更重要的是,将对象用作哈希映射会导致混淆和安全风险。不必要的继承在ES6之前,获得哈希映射的唯一方法是创建一个空对象:consthashMap={}然而,在创建时,这个对象不再是空的。即使hashMap是用一个空的对象字面量创建的,它也会自动继承自Object.prototype。这就是为什么我们可以在hashMap上调用hasOwnProperty、toString、constructor等方法,即使我们从未在该对象上显式定义这些方法。由于原型继承,我们现在有两种类型的属性被混淆:存在于对象本身的属性,即它自己的属性,以及存在于原型链上的属性,即继承的属性。因此,我们需要额外的检查(例如hasOwnProperty)来确保给定的属性确实是用户提供的,而不是从原型继承的。除此之外,由于属性解析机制在JavaScript中的工作方式,在运行时对Object.prototype的任何更改都会在所有对象中引起连锁反应。这为原型污染攻击打开了大门,这对于大型JavaScript应用程序来说是一个严重的安全问题。不过,我们可以通过使用Object.create(null)来解决这个问题,它可以生成一个不继承Object.prototype的对象。名称冲突当对象自身的属性与其原型上的属性发生名称冲突时,它会破坏预期并导致程序崩溃。例如,我们有一个函数foo,它接受一个对象。functionfoo(obj){//...for(constkeyinobj){if(obj.hasOwnProperty(key)){}}}obj.hasOwnProperty(key)存在可靠性风险:考虑The中的属性解析机制它在JavaScript中的工作方式,如果obj包含开发人员提供的具有相同名称的hasOwnProperty属性,这将对Object.prototype.hasOwnProperty产生影响。因此,我们不知道在运行时将准确调用哪个方法。可以进行一些防御性编程来防止这种情况发生。例如,我们可以从Object.prototype中“借用”真正的hasOwnProperty:){//...}}}还有一种更短的方法可以在对象字面量上调用此方法,例如{}.hasOwnProperty.call(key),但这也很麻烦。这就是为什么有一个新的静态方法Object.hasOwn。次优人体工程学对象没有提供足够的人体工程学,不能用作哈希映射。许多常见任务无法凭直觉执行。sizeObject没有提供方便的API来获取大小,即属性的数量。此外,关于对象的大小也有一些细微差别:如果您只关心字符串、可枚举键,则可以使用Object.keys()将键转换为数组并获取其长度。如果k只想要不可枚举的字符串键,则必须使用Object.getOwnPropertyNames来获取键列表并获取它们的长度。如果您只对符号键感兴趣,可以使用getOwnPropertySymbols来显示符号键。或者您可以使用Reflect.ownKeys一次获取字符串键和符号键,无论它是否可枚举。上述所有选项的运行时复杂度都是**O(n)**,因为我们必须先构造一个键数组才能获得它的长度。对对象的迭代循环具有类似的复杂性。我们可以使用for...in循环。但它会读取继承的可枚举属性。Object.prototype.foo='bar'constobj={id:1}for(constkeyinobj){console.log(key)//'id','foo'}我们不能在对象上使用for...of因为默认情况下它不是可迭代对象,除非我们在其上显式定义Symbol.iterator方法。我们可以使用Object.keys、Object.values和Object.entry来获取字符串键(或/和值)的可枚举列表并遍历该列表,这会引入额外的开销步骤。还有一个就是插入对象的key的顺序不符合我们的顺序,这是一个很蛋疼的地方。在大多数浏览器中,整数键按升序排序并优先于字符串键,即使字符串键插入整数键之前:constobj={}obj.foo='first'obj[2]='second'obj[1]='last'console.log(obj)//{1:'last',2:'second',foo:'first'}clear没有简单的方法来删除一个对象的所有属性,我们必须删除使用删除运算符逐一删除每个属性,这在历史上是出了名的慢。检查属性是否存在最后,我们不能依赖点/括号表示法来检查属性是否存在,因为值本身可能被设置为未定义。相反,请使用Object.prototype.hasOwnProperty或Object.hasOwn。constobj={a:undefined}Object.hasOwn(obj,'a')//trueMapES6给我们带来了Map。首先,不同于Object只允许key值是字符串和符号,Map支持任何数据类型的key。但更重要的是,Map提供了用户定义和内置程序数据之间的清晰分离,代价是需要额外的Map.prototype.get来获取相应的项。地图还提供更好的人体工程学。Map默认是一个可迭代对象。这表明您可以轻松地使用for...of遍历Map并执行诸如使用嵌套解构从Map中获取第一项的操作。const[[firstKey,firstValue]]=map与Object相比,Map为各种常见任务提供了专门的API:Map.prototype.has检查给定项是否存在,并且必须在对象上使用Object。prototype.hasOwnProperty/Object.hasOwn没那么尴尬。Map.prototype.get返回与提供的键关联的值。有些人可能会发现这比对象上的点符号或括号符号更麻烦。但是,它提供了用户数据和内置方法之间的清晰分离。Map.prototype.size返回Map中的项数,这明显优于获取对象的大小。而且,它更快。Map.prototype.clear可以删除Map中的所有item,比delete操作符快很多。性能差异在JavaScript社区中,似乎普遍认为在大多数情况下Maps比Objects更快。有些人声称通过从Object切换到Map可以看到显着的性能提升。我也在LeetCode上证实了这个想法。数据量大的对象会超时,但地图不会。然而,说“Map比Object快”可能被认为是归纳的。两者之间肯定有一些细微的差别,我们可以通过一些例子来发现。test测试用例有一张表,主要测试Object和Map的插入、迭代、删除数据的速度。插入和迭代性能以每秒操作数来衡量。此处使用实用函数measureFor重复运行目标函数,直到达到指定的最小时间阈值(即UI上的持续时间输入字段)。它返回每秒执行此类函数的平均次数。functionmeasureFor(f,duration){让迭代次数=0;constnow=performance.now();让经过=0;while(elapsed
