JavaScript中的Map vs Object

我刚刚发现这个特性:

Map: Map对象是简单的键/值映射。

这让我很困惑。常规的JavaScript对象是字典,那么Map和字典有什么不同呢?从概念上讲,它们是相同的(根据另一个关于Stack Overflow的问题)

文档也没有帮助:

Map对象是键/值对的集合,其中键和值都可以是任意的ECMAScript语言值。不同的键值只能出现在Map集合中的一个键/值对中。使用创建Map时选择的比较算法进行区分的不同键值。

Map对象可以按插入顺序迭代其元素。Map对象必须使用哈希表或其他机制来实现,这些机制提供的访问时间平均与集合中元素的数量呈次线性关系。本Map对象规范中使用的数据结构仅用于描述Map对象所需的可观察语义。它并不是一个可行的实现模型。

听起来还是像个物件,显然我错过了什么。

为什么JavaScript获得了一个(受良好支持的)Map对象?它能做什么?

278852 次浏览

根据MDN:

Map对象可以按插入顺序迭代其元素——for..of循环每次迭代将返回一个[key, value]数组。

而且

对象与映射相似,都允许你设置值的键, 检索这些值,删除键,并检测是否存在某些内容 存储在一个键上。因此,对象被用作地图 历史上;然而,对象之间有重要的区别 和地图,使使用地图更好 一个对象有一个原型,所以在映射中有默认键。 但是,这可以使用map = Object.create(null)来绕过。的 对象的键是字符串,它们可以是Map的任何值。 您可以很容易地获得Map的大小,而您必须手动保存

Map

按顺序迭代是开发人员一直想要的功能,部分原因是它可以确保在所有浏览器中都具有相同的性能。所以对我来说,这是一个大问题。

myMap.has(key)方法和myMap.size属性将特别方便。

除了可以按照定义良好的顺序进行迭代,以及能够使用任意值作为键(除了-0)之外,map还很有用,原因如下:

  • 规范强制map操作平均来说是次线性的。

    object的任何不愚蠢的实现都将使用哈希表或类似的表,因此属性查找可能是平均不变的。那么对象甚至可以比地图更快。但这不是规范所要求的

  • 对象可以有令人讨厌的意外行为。

    例如,假设您没有将任何foo属性设置为新创建的对象obj,因此您希望obj.foo返回undefined。但是foo可以是继承自Object.prototype的内置属性。或者您试图通过使用赋值来创建obj.foo,但Object.prototype中的一些setter运行而不是存储您的值。

    地图可以防止这些事情发生。好吧,除非一些脚本搞砸了Map.prototypeObject.create(null)也可以,但是这样你就失去了简单的对象初始化器语法

这两个技巧可以帮助你决定是使用Map还是Object:

  • 当键在运行时之前未知时,在对象上使用映射 所有的键都是相同类型,所有的值都是相同类型

  • 如果需要将原始值存储为键,则使用map 因为object将每个键都视为字符串,要么是数字值,

    . Boolean值或任何其他原始值
  • 当存在操作单个元素的逻辑时使用对象。

来源:# EYZ0

关键的区别是对象只支持字符串和符号键,而映射则支持或多或少的任何键类型。

如果我做obj[123] = true,然后Object.keys(obj),那么我将得到["123"]而不是[123]。Map会保留键的类型并返回[123],这很好。映射还允许使用对象作为键。传统上,要做到这一点,你必须给对象某种唯一的标识符来散列它们(我想我从来没有在JavaScript中看到过getObjectId这样的东西作为标准的一部分)。地图也能保证顺序的保存,所以它们更适合保存,有时还能帮你节省一些排序的时间。

实际上,在Map和对象之间有一些优点和缺点。对象被紧密地集成到JavaScript的核心中,这使得它们与Map的区别大大超出了关键支持的差异。

一个直接的优势是,您拥有object的语法支持,从而可以轻松访问元素。您还可以使用JSON直接支持它。当用作散列时,得到一个没有任何属性的对象是很烦人的。默认情况下,如果你想使用对象作为一个哈希表,它们将被污染,你将经常在访问属性时调用hasOwnProperty。你可以在这里看到默认情况下对象是如何被污染的,以及如何创建希望未被污染的对象用作哈希值:

({}).toString
toString() { [native code] }
JSON.parse('{}').toString
toString() { [native code] }
(Object.create(null)).toString
undefined
JSON.parse('{}', (k,v) => (typeof v === 'object' && Object.setPrototypeOf(v, null) ,v)).toString
undefined

对象上的污染不仅会使代码变得更烦人、更慢等等,而且还会对安全性产生潜在的影响。

对象不是纯粹的哈希表,但它们正在尝试做更多的事情。你会有像hasOwnProperty这样的头痛,不能轻易获得长度(Object.keys(obj).length)等等。对象不是纯粹用作哈希映射,而是用作动态可扩展对象,因此当您将它们用作纯哈希表时,就会出现问题。

各种常用操作比较/列表:

Object:
var o = {};
var o = Object.create(null);
o.key = 1;
o.key += 10;
for(let k in o) o[k]++;
var sum = 0;
for(let v of Object.values(m)) sum += v;
if('key' in o);
if(o.hasOwnProperty('key'));
delete(o.key);
Object.keys(o).length
Map:
var m = new Map();
m.set('key', 1);
m.set('key', m.get('key') + 10);
m.foreach((k, v) => m.set(k, m.get(k) + 1));
for(let k of m.keys()) m.set(k, m.get(k) + 1);
var sum = 0;
for(let v of m.values()) sum += v;
if(m.has('key'));
m.delete('key');
m.size();

还有一些其他的选项、方法、方法等等,它们有不同的起伏(性能、简洁、可移植、可扩展等)。对象作为语言的核心有点奇怪,所以你有很多静态方法来处理它们。

除了map保留键类型以及能够支持对象作为键的优点之外,它们还与对象所具有的副作用隔离开来。Map是一个纯粹的哈希,在同一时间尝试成为一个对象没有混淆。还可以使用代理函数轻松扩展映射。对象目前有一个代理类,但性能和内存使用是严峻的,事实上,创建自己的代理,看起来像映射对象目前比代理执行得更好。

map的一个重大缺点是JSON不直接支持它们。解析是可能的,但它有几个难题:

JSON.parse(str, (k,v) => {
if(typeof v !== 'object') return v;
let m = new Map();
for(k in v) m.set(k, v[k]);
return m;
});

上述操作将严重影响性能,也不支持任何字符串键。JSON编码甚至更加困难和有问题(这是许多方法之一):

// An alternative to this it to use a replacer in JSON.stringify.
Map.prototype.toJSON = function() {
return JSON.stringify({
keys: Array.from(this.keys()),
values: Array.from(this.values())
});
};

如果你纯粹使用map,这还不是很糟糕,但是当你混合类型或使用非标量值作为键时就会出现问题(并不是说JSON是完美的,因为它是IE循环对象引用)。我还没有对它进行测试,但与stringify相比,它可能会严重损害性能。

其他脚本语言通常不会有这样的问题,因为它们为Map、Object和Array提供了显式的非标量类型。Web开发通常是非标量类型的痛苦,你必须处理一些事情,比如PHP使用a /M将数组/Map与对象合并为属性,JavaScript将Map/Object与数组合并为扩展M/O。合并复杂类型是高级脚本语言的魔鬼祸害。

到目前为止,这些主要是围绕实现的问题,但基本操作的性能也很重要。性能也很复杂,因为它取决于引擎和使用情况。对我的测试持保留态度,因为我不能排除任何错误(我必须赶时间)。您还应该运行自己的测试来确认,因为我的测试只检查非常具体的简单场景,只提供粗略的指示。根据Chrome中对非常大的对象/映射的测试,对象的性能更差,因为删除显然与键的数量成正比,而不是O(1):

Object Set Took: 146
Object Update Took: 7
Object Get Took: 4
Object Delete Took: 8239
Map Set Took: 80
Map Update Took: 51
Map Get Took: 40
Map Delete Took: 2

Chrome显然在获取和更新方面有很强的优势,但删除性能非常糟糕。在这种情况下,映射使用了少量的内存(开销),但是只有一个对象/Map要测试数百万个键,映射开销的影响没有很好地表达出来。如果我正确阅读配置文件,内存管理对象似乎也更早释放,这可能是有利于对象的一个好处。

在Firefox中,这是一个不同的故事:

Object Set Took: 435
Object Update Took: 126
Object Get Took: 50
Object Delete Took: 2
Map Set Took: 63
Map Update Took: 59
Map Get Took: 33
Map Delete Took: 1

我应该立即指出,在这个特定的基准测试中,从Firefox中的对象删除不会引起任何问题,但是在其他基准测试中,它会引起问题,特别是当有很多键时,就像在Chrome中一样。在Firefox中,地图对于大型集合来说显然更胜一筹。

然而,这并不是故事的结束,那么许多小物体或地图呢?我已经做了一个快速的基准测试,但不是一个详尽的(设置/获取),它在上面的操作中使用少量的键执行得最好。这个测试更多的是关于内存和初始化。

Map Create: 69    // new Map
Object Create: 34 // {}

这些数字也有所不同,但基本上Object有一个很好的领先。在某些情况下,物体比地图的优势是极端的(10倍),但平均而言,它大约是2-3倍。极端的性能峰值似乎可以双向发挥作用。我只在Chrome和创建中测试了这一点,以配置内存使用情况和开销。我很惊讶地发现,在Chrome中,带有一键的地图使用的内存是带有一键的对象的30倍。

使用以上所有操作(4个键)测试许多小对象:

Chrome Object Took: 61
Chrome Map Took: 67
Firefox Object Took: 54
Firefox Map Took: 139

在内存分配方面,它们在释放/GC方面表现相同,但Map使用了5倍多的内存。这个测试使用了四个键,而在上次测试中,我只设置了一个键,所以这可以解释内存开销的减少。我运行了几次这个测试,Map/Object在整体速度方面对Chrome来说或多或少是不分上下的。在用于小对象的Firefox中,总体上比地图有明显的性能优势。

当然,这还不包括个体选择,因为个体选择可能会有很大的差异。我不建议使用这些数据进行微优化。从中可以得到的经验是,对于非常大的键值存储,更强烈地考虑map,而对于小的键值存储,更强烈地考虑对象。

除此之外,这两者的最佳策略是先实现它,然后让它工作。在分析时,重要的是要记住,有时你认为不会慢的东西,当看到它们时,可能会非常慢,因为引擎的怪叫,如对象键删除情况所见。

除了其他答案之外,我还发现map比对象操作起来更笨拙和冗长。

obj[key] += x
// vs.
map.set(map.get(key) + x)

这很重要,因为更短的代码读起来更快,表达更直接,一直在程序员的脑海里更好。

另一方面:因为set()返回的是映射,而不是值,所以不可能链式赋值。

foo = obj[key] = x;  // Does what you expect
foo = map.set(key, x)  // foo !== x; foo === map

调试地图也更加痛苦。下图中,你实际上看不到地图中的键是什么。你必须编写代码才能做到这一点。

Good luck evaluate a Map Iterator

对象可以被任何IDE计算:

WebStorm评估一个对象

我认为到目前为止答案中还没有提到以下几点,我认为它们值得一提。


地图可以更大

在Chrome中,我可以获得16.7百万键/值对与Map vs. 11.1百万与常规对象。加上Map号,几乎多出50%的配对。它们在崩溃前都占用了大约2gb的内存,所以我认为可能与chrome的内存限制有关(是的,尝试填充2 Maps,在崩溃前你只能得到830万对)。你可以用下面的代码自己测试它(显然,分开运行它们,而不是同时运行):

var m = new Map();
var i = 0;
while(1) {
m.set(((10**30)*Math.random()).toString(36), ((10**30)*Math.random()).toString(36));
i++;
if(i%1000 === 0) { console.log(i/1000,"thousand") }
}
// versus:
var m = {};
var i = 0;
while(1) {
m[((10**30)*Math.random()).toString(36)] = ((10**30)*Math.random()).toString(36);
i++;
if(i%1000 === 0) { console.log(i/1000,"thousand") }
}

对象已经有了一些属性/键

这个问题以前让我犯过错误。常规对象有toStringconstructorvalueOfhasOwnPropertyisPrototypeOf和其他一堆预先存在的属性。对于大多数用例来说,这可能不是一个大问题,但它曾经给我带来过问题。

地图会变慢:

由于.get函数调用开销和缺乏内部优化,对于某些任务,映射可能会慢很多比映射一个普通的旧JavaScript对象更好。

object的行为类似于字典,因为JavaScript是动态类型的,允许您随时添加或删除属性。

但是Map()更好,因为它:

  • 提供getsethasdelete方法。
  • 接受任何类型的键,而不仅仅是字符串。
  • 提供一个方便使用for-of的迭代器,并维护结果的顺序。
  • 在迭代或复制过程中不会出现原型和其他属性的边缘情况。
  • 支持数百万项。
  • 非常快。

如果你需要字典,那么使用Map()

但是,如果您只使用基于字符串的键,并且需要最大的读取性能,那么对象可能是更好的选择。这是因为JavaScript引擎将对象编译成c++类在后台,属性的访问路径比Map().get()的函数调用快得多。

这些类也被缓存,所以创建一个具有完全相同属性的新对象意味着引擎将重用一个现有的后台类。添加或删除一个属性会导致要更改的类的形状和要重新编译的支持类,这就是为什么将一个对象用作添加和删除大量内容的字典会非常慢,但是在不更改对象的情况下读取现有键会非常快。

因此,如果您有一个写一次读的繁重的工作负载和字符串键,那么您可以使用object作为高性能字典,但对于其他任何事情,请使用Map()

简介:

  • Object:数据以键值对的形式存储的数据结构。在对象中,键必须是数字、字符串或符号。值可以是任何东西,也可以是其他对象、函数等。对象是nonordered数据结构,即键值对的插入顺序不被记住
  • ES6 Map:数据以键值对的形式存储的数据结构。其中唯一键映射到值.;键和值都可以在任何数据类型中。map是一个可迭代的数据结构。这意味着插入的顺序被记住,并且我们可以访问例如for..of循环中的元素。

关键的不同点:

  • a# EYZ0是有序且可迭代的,而A对象是无序且不可迭代的(也就是说它们没有[符号]。迭代器]属性。但是,你可以在语法中使用for..遍历键。)

  • 我们可以把任何类型的数据作为Map键,而对象只能有数字、字符串或符号作为键。

  • Map继承自Map.prototype。这提供了各种实用函数和属性,使使用Map对象更加容易。

例子:

对象:

let obj = {};


// adding properties to a object
obj.prop1 = 1;
obj[2]    =  2;


// getting nr of properties of the object
console.log(Object.keys(obj).length)


// deleting a property
delete obj[2]


console.log(obj)

地图:

const myMap = new Map();


const keyString = 'a string',
keyObj = {},
keyFunc = function() {};


// setting the values
myMap.set(keyString, "value associated with 'a string'");
myMap.set(keyObj, 'value associated with keyObj');
myMap.set(keyFunc, 'value associated with keyFunc');


console.log(myMap.size); // 3


// getting the values
console.log(myMap.get(keyString));    // "value associated with 'a string'"
console.log(myMap.get(keyObj));       // "value associated with keyObj"
console.log(myMap.get(keyFunc));      // "value associated with keyFunc"


console.log(myMap.get('a string'));   // "value associated with 'a string'"
// because keyString === 'a string'
console.log(myMap.get({}));           // undefined, because keyObj !== {}
console.log(myMap.get(function() {})) // undefined, because keyFunc !== function () {}

来源:MDN

这是我记住它的一个简单方法:KOI

  1. 钥匙。对象键是字符串或符号。地图键也可以是数字(1和"1"是不同的),对象,NaN等。它使用===来区分键,只有NaN !== NaN例外,但您可以使用NaN作为键。
  2. 秩序。插入顺序被记住。所以[...map][...map.keys()]有一个特定的顺序。
  3. 接口。对象:obj[key]obj.a(在某些语言中,[][]=实际上是接口的一部分)。地图有get()set()has()delete()等。注意,您可以使用map[123],但这是将它作为一个普通的JavaScript对象使用。

根据Mozilla

JavaScript中的对象与地图简短的例子。

对象-遵循与map相同的概念,即使用键值对存储数据。但也有细微的差异,使得地图在某些情况下表现更好。

地图-是一个数据结构,它有助于以对的形式存储数据。这一对由一个唯一键和映射到该键的值组成。这有助于防止口是心非。

<强> < / >强关键差异

  • Map是对象的实例,反之则不然。

var map = new Map();
var obj = new Object();
console.log(obj instanceof Map);   // false
console.log(map instanceof Object);  // true

  • 在Object中,键字段的数据类型被限制为整数、字符串和符号。而在Map中,键字段可以是任何数据类型(整数、数组、对象)

var map = new Map();//Empty
map.set(1,'1');
map.set('one', 1);
map.set('{}', {name:'Hello, World!'});
map.set(12.3, 12.3)
map.set([12],[12345])


for(let [key,value] of map.entries())
console.log(key+'---'+value)

  • 在Map中,元素的原始顺序被保留。这在对象的情况下是不正确的。

let obj ={
1:'1',
'one':1,
'{}': {name:'Hello world'},
12.3:12.3,
[12]:[100]
}
console.log(obj)

什么时候使用映射而不是简单的JavaScript对象

JavaScript Object {key: 'value'}保存结构化数据。但是普通的JavaScript对象有其局限性:

  1. 只有字符串和符号可以作为对象的键。如果我们使用其他东西,比如数字作为对象的键,那么在访问这些键时,我们会看到这些键会隐式地转换为字符串,导致我们失去类型的一致性。# EYZ0

  2. 通过编写JavaScript标识符作为对象的键名(例如,toStringconstructor等),可能会意外地覆盖从原型继承的属性。

  3. 另一个对象不能用作对象的键,因此不能通过将一个对象写入另一个对象的键来为该对象写入额外的信息,而另一个对象的值将包含额外的信息

  4. 对象不是迭代器

  5. 对象的大小不能直接确定

对象的这些局限性可以通过映射来解决,但我们必须将映射视为对象的补充而不是替代。基本上Map只是数组的数组,但我们必须将该数组作为参数传递给Map对象,并使用new关键字,否则仅对于数组数组,Map的有用属性和方法不可用。记住数组中的数组或Map中的键值对必须用逗号分隔,不能像普通对象那样用冒号分隔。

决定使用Map还是Object的三个技巧

  1. 当键在运行时之前是未知的时,在对象上使用map,因为由用户输入形成的键或在不知情的情况下,如果这些键覆盖了对象的继承属性,则会破坏使用对象的代码,因此map在这些情况下更安全。当所有键都是相同类型且所有映射都是相同类型时,也要使用映射。

  2. 如果需要将原始值存储为键,则使用映射。

  3. 如果需要操作单个元素,则使用对象。

使用地图的好处

1. Map接受任何键类型,并保留键类型:

我们知道,如果对象的键不是字符串或符号,那么JavaScript会隐式地将其转换为字符串。相反,Map接受任何类型的键:字符串、数字、布尔值、符号。等,Map保留原来的键类型。在这里,我们将在Map中使用number作为键,它将保持为数字:

    const numbersMap= new Map();
numbersMap.set(1, 'one');
numbersMap.set(2, 'two');
const keysOfMap= [...numbersMap.keys()];


console.log(keysOfMap);                        // [1, 2]

在Map中,我们甚至可以使用整个对象作为键。有时,我们希望存储一些与对象相关的数据,而不将这些数据附加到对象本身中,以便我们可以使用精简对象,但希望存储关于对象的一些信息。在这种情况下,我们需要使用Map,这样我们就可以将Object作为键,并将对象的相关数据作为值。

    const foo= {name: foo};
const bar= {name: bar};
const kindOfMap= [[foo, 'Foo related data'], [bar, 'Bar related data']];

但是这种方法的缺点是通过键访问值的复杂性,因为我们必须遍历整个数组才能获得所需的值。

    function getBy Key(kindOfMap, key) {
for (const [k, v]  of kindOfMap) {
if(key === k) {
return v;
}
}
return undefined;
}
getByKey(kindOfMap, foo);            // 'Foo related data'

我们可以通过使用适当的Map来解决不能直接访问值的问题。

    const foo= {name: 'foo'};
const bar= {name: 'bar'};
const myMap= new Map();
myMap.set(foo, 'Foo related data');
myMap.set(bar, 'Bar related data');


console.log(myMap.get(foo));            // 'Foo related data'

我们本可以使用WeakMap来实现,只需要编写const myMap= new WeakMap()即可。Map和WeakMap之间的区别在于,WeakMap允许键(这里是对象)的垃圾收集,因此它可以防止内存泄漏,WeakMap只接受对象作为键,并且WeakMap减少了方法集。

2. Map对键名没有限制:

对于普通的JavaScript对象,我们可能会意外地覆盖从原型继承的属性,这可能是危险的。这里我们将覆盖actor对象的toString()属性:

    const actor= {
name: 'Harrison Ford',
toString: 'Actor: Harrison Ford'
};

现在让我们定义一个函数,isPlainObject(),来确定所提供的参数是否是一个普通对象,这个函数使用toString()方法来检查它:

    function isPlainObject(value) {
return value.toString() === '[object Object]';
}


isPlainObject(actor);        // TypeError : value.toString is not a function


// this is because inside actor object `toString` property is a
// string instead of inherited method from prototype

Map对键名没有任何限制。我们可以使用键名,如toStringconstructor等,虽然actorMap对象有一个名为toString的属性,但方法toString()继承自actorMap对象的原型完美工作。

    const actorMap= new Map();
actorMap.set('name', 'Harrison Ford');
actorMap.set('toString', 'Actor: Harrison Ford');
function isMap(value) {
return value.toString() === '[object Map]';
}


console.log(isMap(actorMap));     // true

如果我们遇到用户输入创建键的情况,那么我们必须在Map中而不是普通对象中获取这些键。这是因为用户可能会选择一个自定义字段名,如toStringconstructor等,那么在普通对象中这样的键名可能会破坏以后使用该对象的代码。所以正确的解决方案是将用户界面状态绑定到map上,没有办法破坏map:

    const userCustomFieldsMap= new Map([['color', 'blue'],
['size', 'medium'], ['toString', 'A blue box']]);

3.Map是可迭代的:

要迭代普通对象的属性,我们需要Object.entries()Object.keys()Object.entries(plainObject)返回一个从对象中提取的键值对数组,然后我们可以变性这些键和值,并可以得到正常的键和值输出。

    const colorHex= {
'white': '#FFFFFF',
'black': '#000000'
}


for(const [color, hex] of Object.entries(colorHex)) {
console.log(color, hex);
}
//
'white' '#FFFFFF'
'black' '#000000'

由于Map是可迭代的,这就是为什么我们不需要entries()方法来迭代Map和解构键,值数组可以直接在Map上完成,因为在Map中每个元素都是由逗号分隔的键值对数组。

    const colorHexMap = new Map();
colorHexMap.set('white', '#FFFFFF');
colorHexMap.set('black', '#000000');


for(const [color, hex] of colorHexMap) {
console.log(color, hex);
}
//'white' '#FFFFFF'   'black' '#000000'

此外,map.keys()返回键的迭代器,map.values()返回值的迭代器。

4. 我们很容易知道地图的大小

我们不能直接确定一个普通对象中属性的数量。我们需要一个辅助函数,如Object.keys(),它返回一个包含对象键的数组,然后使用length属性,我们可以获得键的数量或普通对象的大小。

    const exams= {'John Rambo': '80%', 'James Bond': '60%'};
const sizeOfObj= Object.keys(exams).length;
console.log(sizeOfObj);       // 2

但是在地图的情况下,我们可以使用map.size属性直接访问地图的大小。

    const examsMap = new Map([['John Rambo', '80%'], ['James Bond', '60%']]);


console.log(examsMap.size);

我发现本文由Minko Gechev撰写清楚地解释了主要的区别。

Enter image description here

Map的一个方面在这里没有给予太多关注,那就是查找。根据规格:

Map对象必须使用哈希表或其他方式来实现 平均而言,提供次线性访问时间的机制 关于集合中元素的数量。使用的数据结构 在此Map对象规范中仅用于描述 需要Map对象的可观察语义。这并非有意为之 一个可行的实现模型

对于具有大量项并需要项查找的集合,这将极大地提高性能。

TL;DR -没有指定对象查找,因此它可以按照对象中元素数量的顺序,即O(n)。映射查找必须使用哈希表或类似的方法,因此无论映射大小如何,即O(1),映射查找都是相同的。

除了上面提到的可用性差异,如果你对大型对象的性能差异更感兴趣,普通对象在chrome上设置、更新和删除大量数据时似乎要快2倍。

< p >实验: # EYZ0 < / p >

TL;DR:你不能在对象中使用键' length'来存储值,但在Map中可以。

对于我来说,不使用object而使用Map的实际考虑是,实际上,object的键不是字符串或数字,而是字符串或数字的子集。具体来说,使用与原型的属性名或众所周知的属性冲突的键可能会导致问题。例如,使用键length存储值可能会使使用length键来确定给定对象是否为数组的代码混淆。