Dav*_*ave 242 javascript dictionary ecmascript-6
我刚刚发现了chromestatus.com,在我失去了几个小时之后,发现了这个功能条目:
地图:地图对象是简单的键/值映射.
那让我困惑.常规的JavaScript对象是字典,所以Map与字典有什么不同?从概念上讲,它们是相同的(根据 地图和字典之间的区别是什么?)
文档chromestatus引用也没有帮助:
映射对象是键/值对的集合,其中键和值都可以是任意ECMAScript语言值.不同的键值只能出现在Map集合中的一个键/值对中.使用在创建Map时选择的比较算法区分不同的键值.
Map对象可以按插入顺序迭代其元素.必须使用散列表或其他机制来实现Map对象,这些机制平均提供对集合中元素数量的次线性访问时间.此Map对象规范中使用的数据结构仅用于描述Map对象所需的可观察语义.它不是一个可行的实施模型.
...对我来说仍然听起来像是一个对象,显然我错过了一些东西.
为什么JavaScript获得(受到良好支持的)Map对象?它有什么作用?
小智 232
据mozilla说:
Map对象可以按插入顺序迭代其元素 - for..of循环将为每次迭代返回[key,value]数组.
和
对象类似于Maps,它们都允许您将键设置为值,检索这些值,删除键以及检测某些键是否存储在键中.因此,对象在历史上被用作地图; 但是,对象和地图之间存在重要差异,这使得更好地使用地图.
Object有一个原型,因此地图中有默认键.但是,可以使用map = Object.create(null)绕过此值.Object的键是字符串,它们可以是Map的任何值.您必须手动跟踪对象的大小,才能轻松获得Map的大小.
当密钥未知时直到运行时,并且当所有键都是相同类型且所有值都是相同类型时,请使用对象上的映射.
当存在对各个元素进行操作的逻辑时使用对象.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
有序可迭代性是开发人员长期以来一直需要的功能,部分原因是它确保了所有浏览器的相同性能.所以对我来说这是一个很大的问题.
该myMap.has(key)方法将特别方便,也myMap.size属性.
jgm*_*jgm 93
关键的区别在于Objects只支持字符串键,而Maps支持或多或少的任何键类型.
如果我做obj [123] = true然后是Object.keys(obj)那么我将得到["123"]而不是[123].Map会保留键的类型并返回[123],这很棒.地图还允许您使用对象作为键.传统上要做到这一点你必须给对象一些独特的标识符来哈希它们(我不认为我曾经在JS中看到像getObjectId这样的东西作为标准的一部分).地图还保证了订单的保存,因此更好地保存,有时可以节省您需要做几种.
在地图和实物之间的实践中,有几个优点和缺点.对象获得的优点和缺点都非常紧密地集成到JS的核心中,这使得它们远远超出了关键支持的差异.
一个直接的优势是你有对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
Run Code Online (Sandbox Code Playgroud)
对对象的污染不仅会使代码更烦人,更慢等,而且还会对安全性产生潜在影响.
对象不是纯哈希表,而是试图做更多.你有像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();
Run Code Online (Sandbox Code Playgroud)
还有一些其他选择,接近,方法等,具有不同的起伏(性能,简洁,便携,可扩展等).对象有点奇怪是语言的核心,所以你有很多静态的方法来处理它们.
除了保留键类型的地图的优势以及能够支持对象之类的键作为键之外,它们还与对象所具有的副作用隔离开来.Map是一个纯哈希,对于尝试同时成为一个对象没有任何困惑.也可以使用代理功能轻松扩展地图.Object目前有一个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;
});
Run Code Online (Sandbox Code Playgroud)
以上将导致严重的性能损失,也不会支持任何字符串键.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())
});
};
Run Code Online (Sandbox Code Playgroud)
如果您纯粹使用地图,但是在混合类型或使用非标量值作为键时会出现问题,这并不是很糟糕(并非JSON完全适用于那种问题,IE循环对象引用).我没有对它进行测试,但与stringify相比,它可能会严重影响性能.
其他脚本语言通常没有这样的问题,因为它们具有Map,Object和Array的显式非标量类型.Web开发通常是非标量类型的痛苦,你必须处理诸如PHP将Array/Map与Object合并使用A/M作为属性以及JS合并Map/Object和Array扩展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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
我应该立即指出,在这个特定的基准测试中,从FireFox中的对象中删除不会导致任何问题,但是在其他基准测试中它会引起问题,尤其是当Chrome中存在许多键时.对于大型集合,FireFox中的地图明显优越.
然而,这不是故事的结尾,许多小物件或地图呢?我已经做了一个快速的基准测试,但不是一个详尽的(设置/获取),在上述操作中使用少量键表现最佳.此测试更多关于内存和初始化.
Map Create: 69 // new Map
Object Create: 34 // {}
Run Code Online (Sandbox Code Playgroud)
这些数字再次变化,但基本上Object具有良好的领先优势.在某些情况下,对象超过地图的领先优势是极端的(大约10倍),但平均来说,它大约是2-3倍.似乎极端性能峰值可以双向工作.我只在Chrome和创建中对此进行了测试,以分析内存使用情况和开销.我很惊讶地看到,在Chrome中,使用一个键的地图使用的内存比使用一个键的对象多30倍.
用于测试许多具有上述所有操作的小对象(4个键):
Chrome Object Took: 61
Chrome Map Took: 67
FireFox Object Took: 54
FireFox Map Took: 139
Run Code Online (Sandbox Code Playgroud)
在内存分配方面,这些在释放/ GC方面的表现相同,但Map使用的内存是5倍.这个测试使用了4个键,就像在上一个测试中我只设置了一个键,这样可以解释内存开销的减少.我对这个测试进行了几次测试,并且就总体速度而言,Map/Object在整体上对于Chrome来说或多或少都是颈部和颈部.在用于小型对象的FireFox中,与整体地图相比,它具有明显的性能优势.
这当然不包括可能变化很大的个别选项.我不建议用这些数字进行微观优化.您可以从中获得的是,根据经验,对于非常大的键值存储和小键值存储的对象,更强烈地考虑映射.
除此之外,最好的策略是用这两个来实现它并让它首先工作.在进行分析时,重要的是要记住,有时候看到它们时你不会想到的东西会很慢,因为对象密钥删除情况下会出现引擎怪癖.
小智 21
到目前为止,我认为答案中没有提到以下几点,我认为值得一提.
在chrome中,我可以获得1670万个键/值对,Map而使用常规对象可以获得1110万个键值/值对.几乎完全相同的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") }
}
Run Code Online (Sandbox Code Playgroud)
这个曾经让我绊倒过.有规则物体的有toString,constructor,valueOf,hasOwnProperty,isPrototypeOf和一堆其他已存在的属性.对于大多数用例来说,这可能不是一个大问题,但它之前给我带来了问题.
由于.get函数调用开销和缺乏内部优化,对于某些任务,Map 可能比普通的旧JavaScript对象慢得多.
deb*_*yan 11
何时使用 Maps 而不是纯 JavaScript 对象?
纯 JavaScript 对象 { key: 'value' } 保存结构化数据。但是普通的 JS 对象有其局限性:
只有字符串和符号可以用作对象的键。如果我们使用任何其他东西,比如数字作为对象的键,那么在访问这些键时,我们将看到这些键将被隐式转换为字符串,导致我们失去类型的一致性。常量名称= {1:'一',2:'二'};Object.keys(names); // ['1', '2']
通过将 JS 标识符写入对象的键名(例如 toString、构造函数等),可能会意外覆盖从原型继承的属性。
另一个对象不能用作对象的键,因此通过将该对象写入另一个对象的键,不能为该对象写入额外信息,并且该另一个对象的值将包含额外信息
对象不是迭代器
无法直接确定物体的大小
Objects 的这些限制是由 Maps 解决的,但我们必须将 Maps 视为 Objects 的补充而不是替代品。基本上 Map 只是数组数组,但我们必须将该数组数组作为参数传递给 Map 对象,并使用 new 关键字,否则仅对于数组数组,Map 的有用属性和方法不可用。并记住数组数组或 Map 中的键值对只能用逗号分隔,不能像普通对象那样用冒号分隔。
决定使用 Map 还是 Object 的 3 个提示:
当键在运行时之前未知时,在对象上使用映射,因为如果这些键覆盖了对象的继承属性,由用户输入或在不知不觉中形成的键可能会破坏使用对象的代码,因此在这些情况下映射更安全。当所有键都是相同类型并且所有映射都是相同类型时,也可以使用映射。
如果需要将原始值存储为键,请使用映射。
如果我们需要对单个元素进行操作,请使用对象。
使用地图的好处是:
1. Map 接受任何键类型并保留键的类型:
我们知道,如果对象的键不是字符串或符号,那么 JS 会隐式地将其转换为字符串。相反,Map 接受任何类型的键:字符串、数字、布尔值、符号等,而 Map 保留原始键类型。在这里,我们将使用 number 作为 Map 中的键,它仍然是一个数字:
const numbersMap= new Map();
numbersMap.set(1, 'one');
numbersMap.set(2, 'two');
const keysOfMap= [...numbersMap.keys()];
console.log(keysOfMap); // [1, 2]
Run Code Online (Sandbox Code Playgroud)
在 Map 中,我们甚至可以使用整个对象作为键。有时我们想要存储一些与对象相关的数据,而不是将这些数据附加到对象本身中,这样我们就可以处理精益对象,但想要存储一些关于对象的信息。在这些情况下,我们需要使用 Map 以便我们可以将对象作为键,将对象的相关数据作为值。
const foo= {name: foo};
const bar= {name: bar};
const kindOfMap= [[foo, 'Foo related data'], [bar, 'Bar related data']];
Run Code Online (Sandbox Code Playgroud)
但是这种方法的缺点是通过键访问值的复杂性,因为我们必须遍历整个数组才能获得所需的值。
function getBy Key(kindOfMap, key) {
for (const [k, v] of kindOfMap) {
if(key === k) {
return v;
}
}
return undefined;
}
getByKey(kindOfMap, foo); // 'Foo related data'
Run Code Online (Sandbox Code Playgroud)
我们可以通过使用适当的 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'
Run Code Online (Sandbox Code Playgroud)
我们可以使用 WeakMap 来完成这个,只需要写,const myMap= new WeakMap()。Map 和 WeakMap 之间的区别在于 WeakMap 允许对键(这里是对象)进行垃圾收集,因此它可以防止内存泄漏,WeakMap 只接受对象作为键,而 WeakMap 减少了方法集。
2. Map 对键名没有限制:
对于普通的 JS 对象,我们可能会意外覆盖从原型继承的属性,这可能很危险。这里我们将覆盖 actor 对象的 toString() 属性:
const actor= {
name: 'Harrison Ford',
toString: 'Actor: Harrison Ford'
};
Run Code Online (Sandbox Code Playgroud)
现在让我们定义一个 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
Run Code Online (Sandbox Code Playgroud)
Map 对键名没有任何限制,我们可以使用toString、constructor 等键名。这里虽然actorMap 对象有一个名为toString 的属性,但是从actorMap 对象的prototype 继承的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
Run Code Online (Sandbox Code Playgroud)
如果我们遇到用户输入创建键的情况,那么我们必须将这些键放入 Map 而不是普通对象。这是因为用户可以选择自定义字段名称,如 toString、构造函数等。那么普通对象中的此类键名可能会破坏以后使用该对象的代码。所以正确的解决方案是将用户界面状态绑定到一个地图,没有办法打破 Map :
const userCustomFieldsMap= new Map([['color', 'blue'], ['size', 'medium'], ['toString', 'A blue box']]);
Run Code Online (Sandbox Code Playgroud)
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'
Run Code Online (Sandbox Code Playgroud)
由于 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'
Run Code Online (Sandbox Code Playgroud)
此外,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
Run Code Online (Sandbox Code Playgroud)
但是在 Maps 的情况下,我们可以使用 map.size 属性直接访问 Map 的大小。
const examsMap= new Map([['John Rambo', '80%'], ['James Bond', '60%']]);
console.log(examsMap.size);
Run Code Online (Sandbox Code Playgroud)
对象的行为就像字典一样,因为Javascript是动态键入的,但实际上并非本来就应该如此。
新Map()功能更好,因为它具有常规get/set/has/delete方法,可以接受键的任何类型,而不仅仅是字符串,在迭代时更易于使用,并且不存在带有原型和其他属性的情况。它也非常快,并且随着引擎变得更好而不断提高。99%的时间,您应该只使用Map()。
但是,如果仅使用基于字符串的键并且需要最大的读取性能,则对象可能是更好的选择。详细信息是(几乎所有)javascript引擎在后台将对象编译为C ++类。这些类型通过它们的“概述”进行缓存和重用,因此,当您创建具有相同确切属性的新对象时,引擎将重用现有的背景类。这些类中属性的访问路径经过了非常优化,并且比查找a快得多Map()。
添加或删除属性会导致重新编译缓存的支持类,这就是为什么将对象用作具有大量键添加和删除键的字典非常慢,但是在不更改对象的情况下读取和分配现有键非常快的原因。
因此,如果您有一个带有字符串键的一次写入大量读取的工作负载,则可以将它object用作专用的高性能字典,而对于其他所有内容,请使用Map()。
根据 Mozilla 的说法
通过示例简要介绍JavaScript 中的对象与映射。
Object-遵循与map相同的概念,即使用键值对来存储数据。但存在一些细微的差异,这使得地图在某些情况下表现更好。
Map-是一种数据结构,有助于以对的形式存储数据。该对由唯一的键和映射到该键的值组成。它有助于防止口是心非。
主要差异
var map = new Map();
var obj = new Object();
console.log(obj instanceof Map); // false
console.log(map instanceof Object); // trueRun Code Online (Sandbox Code Playgroud)
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)Run Code Online (Sandbox Code Playgroud)
let obj ={
1:'1',
'one':1,
'{}': {name:'Hello world'},
12.3:12.3,
[12]:[100]
}
console.log(obj)Run Code Online (Sandbox Code Playgroud)
除了其他答案,我发现地图比对象操作更笨拙和冗长.
obj[key] += x
// vs.
map.set(map.get(key) + x)
Run Code Online (Sandbox Code Playgroud)
这很重要,因为更短的代码更快阅读,更直接的表达,并且更好地保存在程序员的头脑中.
另一个方面:因为set()返回地图而不是值,所以不可能链接赋值.
foo = obj[key] = x; // Does what you expect
foo = map.set(key, x) // foo !== x; foo === map
Run Code Online (Sandbox Code Playgroud)
调试地图也比较痛苦.下面,您实际上无法看到地图中的键.你必须编写代码才能做到这一点.
任何IDE都可以评估对象:
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)Run Code Online (Sandbox Code Playgroud)
地图:
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 () {}Run Code Online (Sandbox Code Playgroud)
这是我记忆的一个简短方式:KOI
NaN等。它用于===区分键,但有一个例外NaN !== NaN,但可以用作NaN键。[...map]or[...map.keys()]有特定的顺序。obj[key]或obj.a(在某些语言中,[]和[]=实际上是界面的一部分)。Map 有get()、set()、has()等delete()。请注意,您可以使用map[123],但那是将其用作纯 JavaScript 对象。除了可以按照明确定义的顺序进行迭代以及能够使用任意值作为键( 除外-0)之外,映射还非常有用,原因如下:
该规范强制映射操作平均而言是次线性的。
对象的任何非愚蠢的实现都将使用哈希表或类似的,因此属性查找平均而言可能是恒定的。那么对象甚至可能比地图更快。但这不是规范所要求的。
对象可能会出现令人讨厌的意外行为。
例如,假设您没有foo为新创建的对象设置任何属性obj,因此您期望obj.foo返回 undefined。但foo可能是继承自 的内置属性Object.prototype。或者您尝试obj.foo使用分配来创建,但Object.prototype运行中的某些设置器而不是存储您的值。
地图可以防止此类事情发生。好吧,除非某些脚本与Map.prototype. 并且Object.create(null)也可以工作,但是这样你就失去了简单的对象初始化语法。
| 归档时间: |
|
| 查看次数: |
66524 次 |
| 最近记录: |