利用JavaScript的Map提升性能的方法详解


Posted in Javascript onAugust 14, 2019

前言

在ES6中引入JavaScript的新特性中,我们看到了Set和Map的介绍。与常规对象和Array不同的是,它们是“键控集合(keyed collections)”。这就是说它们的行为有稍许不同,并且在特定的上下文中使用,它们可以提供相当大的性能优势。

在这篇文章中,我将剖析Map,它究竟有何不同,哪里可以派上用场,相比于常规对象有什么性能优势。

Map与常规对象有什么不同

Map和常规对象主要有2个不同之处。

1.无限制的键(Key)

常规JavaScript对象的键必须是String或Symbol,下面的对象说明的这一点:

const symbol = Symbol();
const string2 = 'string2';

const regularObject = {
 string1: 'value1',
 [string2]: 'value2',
 [symbol]: 'value3'
};

相比之下,Map允许你使用函数、对象和其它简单的类型(包括NaN)作为键,如下代码:

const func = () => null;
const object = {};
const array = [];
const bool = false;
const map = new Map();

map.set(func, 'value1');
map.set(object, 'value2');
map.set(array, 'value3');
map.set(bool, 'value4');
map.set(NaN, 'value5');

在链接不同数据类型时,这个特性提供了极大的灵活性。

2.直接遍历

在常规对象中,为了遍历keys、values和entries,你必须将它们转换为数组,如使用Object.keys()、Object.values()和Object.entries(),或者使用for ... in循环,因为常规对象不能直接遍历,另外for ... in循环还有一些限制:它仅仅遍历可枚举属性、非Symbol属性,并且遍历的顺序是任意的。
而Map可以直接遍历,并且由于它是键控集合,遍历的顺序和插入键值的顺序是一致的。你可以使用for ... of循环或forEach方法来遍历Map的entries,如下代码:

for (let [key, value] of map) {
 console.log(key);
 console.log(value);
};
map.forEach((key, value) => {
 console.log(key);
 console.log(value);
});

还有一个好处就是,你可以调用map.size属性来获取键值数量,而对于常规对象,为了做到这样你必须先转换为数组,然后获取数组长度,如:Object.keys({}).length。

Map和Set有何不同

Map的行为和Set非常相似,并且它们都包含一些相同的方法,包括:has、get、set、delete。它们两者都是键控集合,就是说你可以使用像forEach的方法来遍历元素,顺序是按照插入键值排列的。
最大的不同是Map通过键值(key/value)成对出现,就像你可以把一个数组转换为Set,你也可以把二维数组转换为Map:

const set = new Set([1, 2, 3, 4]);
const map = new Map([['one', 1], ['two', 2], ['three', 3], ['four', 4]]);

类型转换

要将Map切换回数组,你可以使用ES6的结构语法:

const map = new Map([['one', 1], ['two', 2]]);
const arr = [...map];

到目前为止,将Map与常规对象的互相转换依然不是很方便,所以你可能需要依赖一个函数方法,如下:

const mapToObj = map => {
 const obj = {};
 map.forEach((key, value) => { obj[key] = value });
 return obj;
};
const objToMap = obj => {
 const map = new Map();
 Object.keys(obj).forEach(key => { map.set(key, obj[key]) });
 return map;
};

但是现在,在八月份ES2019的首次展示中,我们看见了Object引入了2个新方法:Object.entries()和Object.fromEntries(),这可以使上述方法简化许多:

const obj2 = Object.fromEntries(map);
const map2 = new Map(Object.entries(obj));

在你使用Object.fromEntries转换map为object之前,确保map的key在转换为字符串时会产生唯一的结果,否则你将面临数据丢失的风险。

性能测试

为了准备测试,我会创建一个对象和一个map,它们都有1000000个相同的键值。

let obj = {}, map = new Map(), n = 1000000;
for (let i = 0; i < n; i++) {
 obj[i] = i;
 map.set(i, i);
}

然后我使用console.time()来衡量测试,由于我特定的系统和Node.js版本的原因,时间精度可能会有波动。测试结果展示了使用Map的性能收益,尤其是添加和删除键值的时。

查询

let result;
console.time('Object');
result = obj.hasOwnProperty('999999');
console.timeEnd('Object');
// Object: 0.250ms

console.time('Map');
result = map.has(999999);
console.timeEnd('Map');
// Map: 0.095ms (2.6 times faster)

添加

console.time('Object');
obj[n] = n;
console.timeEnd('Object');
// Object: 0.229ms

console.time('Map');
map.set(n, n);
console.timeEnd('Map');
// Map: 0.005ms (45.8 times faster!)

删除

console.time('Object');
delete obj[n];
console.timeEnd('Object');
// Object: 0.376ms

console.time('Map');
map.delete(n);
console.timeEnd('Map');
// Map: 0.012ms (31 times faster!)

Map在什么情况下更慢

在测试中,我发现一种情况常规对象的性能更好:使用for循环去创建常规对象和map。这个结果着实令人震惊,但是没有for循环,map添加属性的性能胜过常规对象。

console.time('Object');
for (let i = 0; i < n; i++) {
 obj[i] = i;
}
console.timeEnd('Object');
// Object: 32.143ms

let obj = {}, map = new Map(), n = 1000000;
console.time('Map');
for (let i = 0; i < n; i++) {
 map.set(i, i);
}
console.timeEnd('Map');
// Map: 163.828ms (5 times slower)

举个例子

最后,让我们看一个Map比常规对象更合适的例子,比如说我们想写一个函数去检查2个字符串是否由相同的字符串随机排序。

console.log(isAnagram('anagram', 'gramana')); // Should return true
console.log(isAnagram('anagram', 'margnna')); // Should return false

有许多方法可以做到,但是这里,map可以帮忙我们创建一个最简单、最快速的解决方案:

const isAnagram = (str1, str2) => {
 if (str1.length !== str2.length) {
  return false;
 }
 const map = new Map();
 for (let char of str1) {
  const count = map.has(char) ? map.get(char) + 1 : 1;
  map.set(char, count);
 }
 for (let char of str2) {
  if (!map.has(char)) {
   return false;
  }
  const count = map.get(char) - 1;
  if (count === 0) {
   map.delete(char);
   continue;
  }
  map.set(char, count);
 }
 return map.size === 0;
};

在这个例子中,当涉及到动态添加和删除键值,无法提前确认数据结构(或者说键值的数量)时,map比object更合适。

我希望这篇文章对你有所帮助,如果你之前没有使用过Map,不妨开阔你的眼界,衡量现代JavaScript的价值体现。

译者注:我个人不太同意作者的观点,从以上的描述来看,Map更像是以空间为代价,换取速度上的提升。那么对于空间和速度的衡量,必然存在一个阈值。在数据量比较少时,相比与速度的提升,其牺牲的空间代价更大,此时显然是不适合使用Map;当数据量足够大时,此时空间的代价影响更小。所以,看开发者如何衡量两者之间的关系,选择最优解。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
基于jquery DOM写的类似微博发布的效果
Oct 20 Javascript
js 使FORM表单的所有元素不可编辑的示例代码
Oct 17 Javascript
jquery使用经验小结
May 20 Javascript
浅谈Javascript中Object与Function对象
Sep 26 Javascript
JS实现部分HTML固定页面顶部随屏滚动效果
Dec 24 Javascript
jQuery实现横向带缓冲的水平运动效果(附demo源码下载)
Jan 29 Javascript
防止Node.js中错误导致进程阻塞的办法
Aug 11 Javascript
微信小程序 简单教程实例详解
Jan 13 Javascript
原生实现一个react-redux的代码示例
Jun 08 Javascript
JavaScript去掉数组重复项的方法分析【测试可用】
Jul 19 Javascript
jquery ajax 请求小技巧实例分析
Nov 11 jQuery
js基础语法与maven项目配置教程案例
Jul 15 Javascript
微信小程序 调用远程接口 给全局数组赋值代码实例
Aug 13 #Javascript
vue源码nextTick使用及原理解析
Aug 13 #Javascript
封装微信小程序http拦截器过程解析
Aug 13 #Javascript
Vue中通过Vue.extend动态创建实例的方法
Aug 13 #Javascript
微信小程序封装分享与分销功能过程解析
Aug 13 #Javascript
node删除、复制文件或文件夹示例代码
Aug 13 #Javascript
vue实现下拉加载其实没那么复杂
Aug 13 #Javascript
You might like
PHP中如何实现常用邮箱的基本判断
2014/01/07 PHP
php中使用url传递数组的方法
2015/02/11 PHP
PHP数组与对象之间使用递归实现转换的方法
2015/06/24 PHP
PHP实现的分解质因数操作示例
2018/08/01 PHP
laravel 如何实现引入自己的函数或类库
2019/10/15 PHP
统计jQuery中各字符串出现次数的工具
2012/05/03 Javascript
SwfUpload在IE10上不出现上传按钮的解决方法
2013/06/25 Javascript
Javascript中引用示例介绍
2014/02/21 Javascript
javascript如何实现360度全景照片问题汇总
2016/04/04 Javascript
使用jQuery Ajax 请求webservice来实现更简练的Ajax
2016/08/04 Javascript
利用Chrome DevTools直接调试Node.js和JavaScript的方法详解(并行)
2017/02/16 Javascript
js获取元素下的第一级子元素的方法(推荐)
2017/03/05 Javascript
JS去掉字符串前后空格或去掉所有空格的用法
2017/03/25 Javascript
微信小程序网络请求wx.request详解及实例
2017/05/18 Javascript
js 显示日期时间的实例(时间过一秒加1)
2017/10/25 Javascript
vue2过滤器模糊查询方法
2018/09/16 Javascript
详解webpack打包vue项目之后生成的dist文件该怎么启动运行
2019/09/06 Javascript
vue 关闭浏览器窗口的时候,清空localStorage的数据示例
2019/11/06 Javascript
环形加载进度条封装(Vue插件版和原生js版)
2019/12/04 Javascript
Auto.JS实现抖音刷宝等刷视频app,自动点赞,自动滑屏,自动切换视频功能
2020/05/08 Javascript
[02:24]DOTA2亚洲邀请赛 NAVI战队出场宣传片
2015/02/07 DOTA
微信 用脚本查看是否被微信好友删除
2016/10/28 Python
python3+PyQt5 创建多线程网络应用-TCP客户端和TCP服务器实例
2019/06/17 Python
Python socket 套接字实现通信详解
2019/08/27 Python
Django之模板层的实现代码
2019/09/09 Python
Python基于xlutils修改表格内容过程解析
2020/07/28 Python
HTML5之SVG 2D入门1—SVG(可缩放矢量图形)概述
2013/01/30 HTML / CSS
美国家用电器和电子产品商店:Abt
2016/09/06 全球购物
澳大利亚潮流尖端的快时尚品牌:Cotton On
2016/09/26 全球购物
静态变量和实例变量的区别
2015/07/07 面试题
房地产开发计划书
2014/01/10 职场文书
文明风采获奖感言
2014/02/18 职场文书
高中升旗仪式演讲稿
2014/09/09 职场文书
关于群众路线的心得体会
2014/11/05 职场文书
2014年生产管理工作总结
2014/12/23 职场文书
兴趣班停课通知
2015/04/24 职场文书