在日常的 JavaScript 项目中,我们最常用到的数据结构就是各种形式的键值对格式了(key-value pair)。在 JavaScript 中,除了最基础的 Object
是该格式外,ES6 新增的 Map
也同样是键值对格式。它们的用法在很多时候都十分接近。不知道有没有人和我一样纠结过该选择哪个去使用呢?在本菜最近的项目中,我又遇到了这样的烦恼,索性一不做二不休,去对比一下究竟该使用哪一个。
本文将会探讨一下 Object
和 Map
的不同,从多个角度对比一下 Object
和 Map
:
希望读完本文的你可以在日后的项目中做出更为合适的选择。
Object
而言,它键(key)的类型只能是字符串,数字或者 Symbol
;而对于 Map
而言,它可以是任何类型。(包括 Date,Map,或者自定义对象)Map
中的元素会保持其插入时的顺序;而 Object
则不会完全保持插入时的顺序,而是根据如下规则进行排序:Symbol
,Symbol
也是根据插入的顺序进行排序的3 . 读取 Map
的长度很简单,只需要调用其 .size()
方法即可;而读取 Object
的长度则需要额外的计算:Object.keys(obj).length
4 . Map 是可迭代对象,所以其中的键值对是可以通过 for of
循环或 .foreach()
方法来迭代的;而普通的对象键值对则默认是不可迭代的,只能通过 for in
循环来访问(或者使用 Object.keys(o)、Object.values(o)、Object.entries(o)
来取得表示键或值的数字)迭代时的顺序就是上面提到的顺序。
const o = {};
const m = new Map();
o[Symbol.iterator] !== undefined; // false
m[Symbol.iterator] !== undefined; // true
5 . 在 Map
中新增键时,不会覆盖其原型上的键;而在 Object
中新增键时,则有可能覆盖其原型上的键:
Object.prototype.x = 1;
const o = {x:2};
const m = new Map([[x,2]]);
o.x; // 2,x = 1 被覆盖了
m.x; // 1,x = 1 不会被覆盖
6 . JSON
默认支持 Object
而不支持 Map
。若想要通过 JSON
传输 Map
则需要使用到 .toJSON()
方法,然后在 JSON.parse()
中传入复原函数来将其复原。
对于 JSON
这里就不具体展开了,有兴趣的朋友可以看一下这:JSON 的序列化和解析
const o = {x:1};
const m = new Map([['x', 1]]);
const o2 = JSON.parse(JSON.stringify(o)); // {x:1}
const m2 = JSON.parse(JSON.stringify(m)) // {}
Obejct
const o = {}; // 对象字面量
const o = new Object(); // 调用构造函数
const o = Object.create(null); // 调用静态方法 Object.create
对于 Object
来说,我们在 95%+ 的情况下都会选择对象字面量,它不仅写起来最简单,而且相较于下面的函数调用,在性能方面会更为高效。对于构建函数,可能唯一使用到的情况就是显式的封装一个基本类型;而 Object.create
可以为对象设定原型。
Map
const m = new Map(); // 调用构造函数
和 Object
不同,Map
没有那么多花里胡哨的创建方法,通常只会使用其构造函数来创建。
除了上述方法之外,我们也可以通过 Function.prototype.apply()、Function.prototype.call()、reflect.apply()、Reflect.construct()
方法来调用 Object
和 Map
的构造函数或者 Object.create()
方法,这里就不展开了。
Obejct
const o = {};
//新增/修改
o.x = 1;
o['y'] = 2;
//读取
o.x; // 1
o['y']; // 2
//或者使用 ES2020 新增的条件属性访问表达式来读取
o?.x; // 1
o?.['y']; // 2
//删除
delete o.b;
对于新增元素,看似使用第一种方法更为简单,不过它也有些许限制:
对于条件属性访问表达式的更多内容可以看一下这:条件属性访问表达式
Map
const m = new Map();
//新增/修改
m.set('x', 1);
//读取
map.get('x');
//删除
map.delete('b');
对于简单的增删查改来说,Map
上的方法使用起来也是十分便捷的;不过在进行联动操作时,Map
中的用法则会略显臃肿:
const m = new Map([['x',1]]);
// 若想要将 x 的值在原有基础上加一,我们需要这么做:
m.set('x', m.get('x') + 1);
m.get('x'); // 2
const o = {x: 1};
// 在对象上修改则会简单许多:
o.x++;
o.x // 2
接下来我们来讨论一下 Object
和 Map
的性能。不知道各位有没有听说过 Map 的性能优于 Object 的说法,我反正是见过不少次,甚至在 JS 高程四中也提到了 Map
对比 Object
时性能的优势;不过对于性能的概括都十分的笼统,所以我打算做一些测试来对比一下它们的区别。
在这里我进行的对于性能测试的都是基于 v8 引擎的。速度会通过 JS 标准库自带的 performance.now()
函数来判断,内存使用情况会通过 Chrome devtool
中的 memory
来查看。
对于速度测试,因为单一的操作速度太快了,很多时候 performance.now()
会返回 0。所以我进行了 10000 次的循环然后判断时间差。因为循环本身也会占据一部分时间,所以以下的测试只能作为一个大致的参考。
测试用的代码如下:
let n, n2 = 5;
// 速度
while (n2--) {
let p1 = performance.now();
n = 10000;
while (n--) { let o = {}; }
let p2 = performance.now();
n = 10000;
while (n--) { let m = new Map(); }
let p3 = performance.now();
console.log(`Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`);
}
// 内存
class Test {}
let test = new Test();
test.o = o;
test.m = m;
首先进行对比的是创建 Object
和 Map
时的表现。对于创建的速度表现如下:
我们可以发现创建 Object
的速度会快于 Map
。对于内存使用情况则如下:
我们主要关注其 Retained Size
,它表示了为其分配的空间。(即删除时释放的内存大小)
通过对比我们可以发现,空的 Object
会比空的 Map
占用更少的内。所以这一轮 Object
赢得一筹。
测试用的代码如下:
console.clear();
let n, n2 = 5;
let o = {}, m = new Map();
// 速度
while (n2--) {
let p1 = performance.now();
n = 10000;
while (n--) { o[Math.random()] = Math.random(); }
let p2 = performance.now();
n = 10000;
while (n--) { m.set(Math.random(), Math.random()); }
let p3 = performance.now();
console.log(`Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`);
}
// 内存
class Test {}
let test = new Test();
test.o = o;
test.m = m;
对于新建元素时的速度表现如下:
我们可以发现新建元素时,Map
的速度会快于 Object
。对于内存使用情况则如下:
通过对比我们可以发现,在拥有一定数量的元素时, Object
会比 Map
占用多了约 78% 的内存。我也进行了多次的测试,发现在拥有足够的元素时,这个百分比是十分稳定的。所以说,在需要进行很多新增操作,且需要储存许多数据的时候,使用 Map
会更高效。
测试用的代码如下:
let n;
let o = {}, m = new Map();
n = 10000;
while (n--) { o[Math.random()] = Math.random(); }
n = 10000;
while (n--) { m.set(Math.random(), Math.random()); }
let p1 = performance.now();
for (key in o) { let k = o[key]; }
let p2 = performance.now();
for ([key] of m) { let k = m.get(key); }
let p3 = performance.now();
`Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`
对于读取元素时的速度表现如下:
通过对比,我们可以发现 Object
略占优势,但总体差别不大。
不知道大家是否听说过 delete
操作符性能低下,甚至有很多时候为了性能,会宁可将值设置为 undefined
而不使用 delete
操作符的说法。但其实在 v8
近来的优化下,它的效率已经提升许多了。
测试用的代码如下:
let n;
let o = {}, m = new Map();
n = 10000;
while (n--) { o[Math.random()] = Math.random(); }
n = 10000;
while (n--) { m.set(Math.random(), Math.random()); }
let p1 = performance.now();
for (key in o) { delete o[key]; }
let p2 = performance.now();
for ([key] of m) { m.delete(key); }
let p3 = performance.now();
`Object: ${(p2 - p1).toFixed(3)}ms, Map: ${(p3 - p2).toFixed(3)}ms`
对于删除元素时的速度表现如下:
我们可以发现在进行删除操作时,Map
的速度会略占优,但整体差别其实并不大。
其实除了最基本的情况之外,还有一种特殊的情况。还记得我们在前面提到的 Object
中键的排序吗?我们提到了其中的非负整数会被最先列出。其实对于非负整数作为键的值和其余类型作为键的值来说,v8
是会对它们进行区别对待的。负整数作为键的部分会被当成数组对待,即非负整数具有一定的连续性时,会被当成快数组,而过于稀疏时会被当成慢数组。
对于快数组,它拥有连续的内存,所以在进行读写时会更快,且占用更少的内存。更多的内容可以看一下这: 探究JS V8引擎下的“数组”底层实现
在键为连续非负整数时,性能如下:
我们可以看到 Object
不仅平均速度更快了,其占用的内存也大大减少了。
通过对比我们可以发现,Map
和 Object
各有千秋,对于不同的情况下,我们应当作出不同的选择。所以我总结了一下我认为使用 Map
和 Object
更为合适的时机。
使用 Map
:
Symbol
时,选择 Map
,因为 Object
并不支持Map
,因为它占用的内存更小Map
,因为速度更快Map
,因为 Object
会改变排序Map
,因为它默认是可迭代对象,迭代更为便捷使用 Object
:
Object
,因为它在数据少的时候占用内存更少,且新建时更为高效JSON
进行文件传输时,选择 Object
,因为 JSON
不默认支持 Map
Object
,因为句法更为简洁Object
虽然 Map
在很多情况下会比 Object
更为高效,不过 Object
永远是 JS
中最基本的引用类型,它的作用也不仅仅是为了储存键值对。
探究JS V8引擎下的“数组”底层实现
Fast properties in V8
Shallow, Retained, and Deep Size
Slow delete of object properties in JS in V8
ES6 — Map vs Object — What and when?
JavaScript高级程序设计(第4版)
JavaScript: The Definitive Guide (7th Edition)
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/nRyIExcTE_voVb4ZF1EpTg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。