JavaScript中的地图与对象


289

我刚刚发现了chromestatus.com,在失去了一天的时间后,发现了这个功能条目

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

那让我感到困惑。常规JavaScript对象是字典,那么它Map与字典有何不同?从概念上讲,它们是相同的(根据 Map和Dictionary之间的区别是什么?

chromestatus文档参考无济于事:

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

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

…对我来说听起来仍然像是一个对象,所以很明显我错过了一些东西。

为什么JavaScript获得了一个(被良好支持的)Map对象?它有什么作用?


Answers:


285

根据mozilla:

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

对象与Maps相似,两者都允许您将键设置为值,检索这些值,删除键并检测是否在键处存储了某些内容。因此,对象在历史上一直被用作地图;但是,对象和地图之间存在重要差异,这使得使用地图更好。

对象具有原型,因此地图中包含默认键。但是,可以使用map = Object.create(null)绕过此操作。对象的键是字符串,其中键可以是Map的任何值。您可以轻松获取地图的大小,而不必手动跟踪对象的大小。

当键直到运行时才是未知的,并且所有键都是相同的类型并且所有值都是相同的类型时,请在对象上使用映射。

当存在对单个元素进行操作的逻辑时,请使用对象。

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Map

有序可迭代性是开发人员长期以来一直希望使用的功能,部分原因是它确保了所有浏览器的相同性能。所以对我来说,那是很大的。

myMap.has(key)方法将特别方便,而且myMap.size属性也很方便。


13
不利的一面是,为了保持插入顺序,映射需要更多的内存(但是在相同的数量级内)。
John Kurlak 2014年

4
除了这里提到的有序性之外,地图还具有其他功能(使用任何对象作为键,键和道具的分离等),但是在某些情况下,FWIW由ES2015定义了普通对象属性的迭代顺序。请参阅stackoverflow.com/a/32149345
JMM 2015年

2
当您说“对象具有原型”时,map = Object.create(null)我没有得到这个含义,因此地图中有默认键。但是,可以使用绕过此操作。什么是默认密钥?按键如何关联Object.prototype
外汇兑换

4
我在Chrome浏览器中进行的测试表明,地图在维护秩序方面不会使用任何大量内存。我认为一百万个密钥还有0.1KB,我不认为这是为了维护顺序。但是,〜0.1KB似乎是恒定的开销。如果用一个键创建一百万张地图,然后比较它比对象大得多。
jgmjgm

2
@luxon,您正在此处创建对象。ES6规范要求将new运算符与Map符号一起使用,即new Map为其创建地图对象。var a = {}是(等于)的简写var a = Object.create(Object.prototype)
-dudewad

103

关键区别在于对象仅支持字符串键,而地图则或多或少地支持任何键类型。

如果我这样做obj[123] = true,那么Object.keys(obj)我会得到,["123"]而不是[123]。Map会保留键的类型并返回[123]很大的值。地图还允许您将对象用作键。传统上,您必须为对象提供某种唯一的标识符以对它们进行哈希处理(我认为我从未见过像getObjectIdJS这样的标准中的任何东西)。地图还可以保证顺序的保存,因此更好地进行保存,有时可以省去一些需要做的事情。

在实践中,地图和对象之间有很多优点和缺点。对象非常紧密地集成到JavaScript的核心中,既有优点也有缺点,这使它们与Map的区别远远超出了关键支持的区别。

直接的好处是您对对象具有语法支持,从而可以轻松访问元素。您还可以通过JSON直接支持它。当用作哈希时,获取一个完全没有任何属性的对象很烦人。默认情况下,如果要将Objects用作哈希表,它们将被污染,并且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是纯散列,尝试同时成为一个对象没有任何困惑。使用代理功能还可以轻松扩展地图。对象的当前具有Proxy类,但是性能和内存使用情况很差,实际上创建自己的代理看起来像Map for Objects当前比Proxy更好。

Maps的主要缺点是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())
    });
};

如果您仅使用Maps,这还不错,但是当您混合类型或使用非标量值作为键时会遇到问题(不是JSON完美解决了这种问题,即IE循环对象引用)。我还没有测试过,但是相比于字符串化,它很可能会严重损害性能。

其他脚本语言通常不存在此类问题,因为它们具有针对Map,Object和Array的显式非标量类型。对于非标量类型,Web开发通常很麻烦。在这种情况下,您必须处理类似PHP的事情,即使用A / M作为属性将Array / Map与对象合并,而JS将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显然在获取和更新方面具有强大的优势,但删除性能令人震惊。在这种情况下,地图会使用少量的内存(开销),但是只有一个对象/地图已通过数百万个键进行测试,因此无法很好地体现出地图开销的影响。使用内存管理对象,如果我正确地阅读了配置文件,对象似乎也早些释放了,这可能是支持对象的好处之一。

在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倍。该测试使用了4个键,而在上一个测试中,我只设置了一个键,因此可以解释内存开销的减少。我进行了几次测试,在整体速度方面,Chrome的Map / Object总体上与并驾齐驱。在Firefox中,对于小型对象,与整体地图相比,它具有明显的性能优势。

当然,这不包括可能有很大差异的单个选项。我不建议对这些数字进行微优化。您可以从中得到的经验是,根据经验,对于较大的键值存储区,最好考虑使用Maps;对于较小的键值存储区,最好考虑使用对象。

除此之外,这两个最佳策略是实施它并使其首先起作用。在进行概要分析时,请务必牢记,有时由于对象键删除案例中出现的引擎怪异,您在查看它们时不会觉得很慢的事情可能会变得非常慢。


缺乏可序列化性一直是许多开发人员的真正痛苦。看看“ 如何在本地存储中(或其他地方)持久保存ES6地图”的观点?以及如何JSON.stringify ES6 Map?
富兰克林·于

数字是毫秒,字节还是对象总数?
StefansArya

这样说来,女士(花了一点时间来表示已用过的东西,因此在这种情况下会用完时间)。尽管这是一个古老的测试,并且我不再有基准代码。现在可能已经非常不同了。例如,我认为删除问题已解决。
jgmjgm

27

我认为到目前为止,答案中并未提及以下几点,我认为它们值得一提。


地图可以更大

在chrome中,与常规对象相比,我可以获得1,670万个键/值对,Map而与之相比,我可以得到1,110万。几乎有50%的配对带有Map。它们在崩溃之前都占用了大约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函数调用的开销和内部优化的不足,对于某些任务,Map 可能比普通的旧JavaScript对象要得多。


1
您认为语义在这里是否胜过性能?如果您需要字典,地图听起来很完美,但是很难接受较慢的查找。快速查询不是整个词典的重点吗?
user2954463

3
我肯定会用普通的旧物件去,如果你是罚款1100万键/值对,并不在乎像预先存在的钥匙toStringconstructor等(即你的密钥是非常不可能的碰撞与他们)。它们更易于使用-例如,增量为obj[i] = (obj[i] || 0) + 1,而Map它的map.set(i, (map.get(i) || 0) + 1)使用还算不错,但这只是说明事情可能变得不必要地混乱。地图肯定有其用例,但是通常一个普通的对象就可以。

请注意,您可以摆脱违约toStringconstructor写,(等)对象的属性obj = Object.create(null),而不是obj = {}

17

对象的行为就像字典一样,因为Javascript是动态键入的,因此您可以随时添加或删除对象的属性。

但是新Map()功能要好得多,因为:

  • 它提供了getsethas,和delete方法。
  • 接受任何类型的键,而不仅仅是字符串。
  • 提供易于for-of使用的迭代器并保持结果的顺序。
  • 在迭代或复制期间没有出现带有原型和其他属性的边缘情况。
  • 它支持数百万个项目。
  • 速度非常快,并且随着javascript引擎的改进而不断提高。

您有99%的时间应该只使用Map()。但是,如果仅使用基于字符串的键并且需要最大的读取性能,则对象可能是更好的选择。

详细信息是(几乎所有)javascript引擎在后台将对象编译为C ++类。这些类型通过它们的“概述”进行缓存和重用,因此,当您创建具有相同确切属性的新对象时,引擎将重用现有的背景类。这些类中属性的访问路径经过了非常优化,并且比查找a快得多Map()

添加或删除属性会导致重新编译缓存的后备类,这就是为什么将对象用作具有大量键添加和删除键的字典非常慢,但是在不更改对象的情况下读取和分配现有键非常快的原因。

因此,如果您有一次使用字符串键进行一次写入的大量读取工作,则可以将它object用作专用的高性能字典,而对于其他所有内容,请使用Map()


对象也提供了get set has delete类似的功能,它不是那么优雅(但也不错)。以哪种方式Map更易于迭代?不确定我是否可以同意。
安德鲁

@Andrew我正在讨论这些方法,并且功能也有所不同,具体取决于您所使用的内容和结果。迭代更容易,因为原型和本机属性不会出现在循环中,而是使用保持相同顺序的普通JS迭代器。
Mani Gandham

11

除了其他答案外,我发现与对象相比,使用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

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

祝您评估Map Iterator好运

可以通过任何IDE评估对象:

WebStorm评估对象


4
考虑到所有这些,似乎map是一个过早的优化。
PRMan

10

摘要:

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

主要区别:

  • A Map是有序且可迭代的,而对象不是有序且不可迭代的

  • 我们可以将任何类型的数据作为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


4

除了可以按定义的顺序进行迭代之外-0,还可以使用任意值作为键(除外),由于以下原因,映射很有用:

  • 该规范将地图操作强制平均化为亚线性。

    对象的任何非愚蠢的实现都将使用哈希表或类似的表,因此属性查询平均而言可能是恒定的。这样,对象甚至可以比地图更快。但这不是规范要求的。

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

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

    地图可防止此类情况。好吧,除非某些脚本弄乱了Map.prototype。并且Object.create(null)也可以,但是随后您失去了简单的对象初始化程序语法。


4

什么时候使用Maps代替普通的JavaScript对象?

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

  1. 只能将字符串和符号用作对象的键。如果我们使用其他任何说法,将数字作为对象的键,则在访问这些键期间,我们将看到这些键将隐式转换为字符串,从而使我们失去类型的一致性。const names = {1:“一个”,2:“两个”};Object.keys(名称); // ['1','2']

  2. 通过将JS标识符写为对象的键名(例如toString,构造函数等),有可能会意外覆盖原型的继承属性。

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

  4. 对象不是迭代器

  5. 无法直接确定对象的大小

Maps解决了Objects的这些限制,但是我们必须将Maps作为Objects的补充而不是替代。基本上,Map只是数组数组,但我们必须将该数组数组作为带有new关键字的参数传递给Map对象,否则,仅对于数组数组,Map的有用属性和方法不可用。请记住,数组或数组中的键/值对必须仅用逗号分隔,没有像普通对象中那样的冒号。

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

  1. 在直到运行时才知道键的情况下,才在对象上使用映射,因为如果这些键覆盖了对象的继承属性,则由用户输入或在不知不觉中形成的键可能会破坏使用该对象的代码,因此在这些情况下map是更安全的。当所有键为相同类型且所有图为相同类型时,也请使用图。

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

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

使用地图的好处是:

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

我们知道,如果对象的键不是字符串或符号,则JS会将其隐式转换为字符串。相反,Map接受任何类型的键:字符串,数字,布尔值,符号等。Map保留原始键类型。在这里,我们将数字用作地图内的键,并且它将保留为数字:

const numbersMap= new Map();

numbersMap.set(1, 'one');

numbersMap.set(2, 'two');

const keysOfMap= [...numbersMap.keys()];

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

在地图内部,我们甚至可以将整个对象用作键。有时,我们可能想存储一些与对象相关的数据,而又不将这些数据附加到对象本身内部,以便我们可以使用精益对象,但又想存储有关该对象的某些信息。在这些情况下,我们需要使用Map,以便我们可以将Object作为键,并将Object的相关数据作为value。

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对键名没有限制:

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

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

现在让我们定义一个fn isPlainObject()来确定提供的参数是否为普通对象,并且该fn使用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对键名没有任何限制,我们可以使用键名,例如toString,构造函数等。在这里,尽管actorMap对象具有名为toString的属性,但是从actorMap对象的原型继承的toString()方法是完美的。

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中而不​​是普通对象中。这是因为用户可以选择自定义字段名称,例如toString,构造函数等。然后,普通对象中的此类键名可能会破坏以后使用该对象的代码。因此正确的解决方案是将用户界面状态绑定到地图,没有办法破坏地图:

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

3.地图是可迭代的:

要迭代普通对象的属性,我们需要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是可迭代的,这就是为什么我们不需要entry()方法来迭代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()的辅助函数fn,它返回一个包含对象键的数组,然后使用length属性,我们可以获得键的数量或普通对象的大小。

const exams= {'John Rambo': '80%', 'James Bond': '60%'};

const sizeOfObj= Object.keys(exams).length;

console.log(sizeOfObj);       // 2

但是对于Maps,我们可以使用map.size属性直接访问Map的大小。

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

console.log(examsMap.size);

1

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

  • 当键直到运行时才是未知的,并且所有键都是相同的类型并且所有值都是相同的类型时,请在对象上使用映射。

  • 如果需要将原始值存储为键,请使用映射,因为对象会将每个键视为字符串,无论其是数字值,布尔值还是任何其他原始值。

  • 当存在对单个元素进行操作的逻辑时,请使用对象。

来源:https : //developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Keyed_Collections#Object_and_Map_compared


2
这些技巧似乎没有什么特别的帮助,尤其是因为按照这些标准将事情划分开来往往不容易。我不明白第一个为什么键/值是相同类型时映射对您有好处。听起来更像是在尝试使用类/结构之类的对象,集合之类的映射。第二篇写得不好,没到重点。这确实意味着在混合了等效字符串类型(“ 1”和1)或需要/想要保留键类型时使用映射。最后一个我认为与第一个相同,它假设您不知道对象是什么,所以它含糊不清。
jgmjgm

0

这是我记住它的一种简短方法:KOI

  1. 按键 对象键是字符串或符号。地图键也可以是数字(1和“ 1”不同),对象,NaN等。它用于===区分键,只有一个例外,NaN !== NaN但您可以NaN用作键。
  2. 订购。记住插入顺序。所以[...map]还是[...map.keys()]有特定顺序的。
  3. 接口。对象:obj[key]obj.a(在某种语言中,[]并且[]=实际上是界面的一部分)。地图有get()set()has()delete()等请注意,您可以使用map[123],但正在使用它作为一个简单的JS对象。

0

根据Mozilla

用示例简短地介绍了JavaScript中的对象与地图

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

Map-是一种数据结构,有助于以成对的形式存储数据。该对包括唯一键和映射到该键的值。它有助于防止重复。

关键差异

  • Map是对象的实例,但反之亦然。

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

  • 在对象中,键字段的数据类型限制为整数,字符串和符号。而在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)

  • 在地图中,保留了元素的原始顺序。对于对象,情况并非如此。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.