利用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包裹节点用法完整示例
Sep 13 Javascript
seajs学习之模块的依赖加载及模块API的导出
Oct 20 Javascript
BootStrap table使用方法分析
Nov 08 Javascript
javascript 中select框触发事件过程的分析
Aug 01 Javascript
angular指令笔记ng-options的使用方法
Sep 18 Javascript
10 种最常见的 Javascript 错误(频率最高)
Feb 08 Javascript
基于jQuery实现的设置文本区域的光标位置
Jun 15 jQuery
Angularjs中的$apply及优化使用详解
Jul 02 Javascript
element-ui 设置菜单栏展开的方法
Aug 22 Javascript
详解KOA2如何手写中间件(装饰器模式)
Oct 11 Javascript
jquery实现掷骰子小游戏
Oct 24 jQuery
js实现限定范围拖拽的示例
Oct 26 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 Error与Logging函数的深入理解
2013/06/03 PHP
xss防御之php利用httponly防xss攻击
2014/03/21 PHP
php中filter_input函数用法分析
2014/11/15 PHP
postfixadmin忘记密码后的修改密码方法详解
2016/07/20 PHP
PHP并发查询MySQL的实例代码
2017/08/09 PHP
php在windows环境下获得cpu内存实时使用率(推荐)
2018/02/08 PHP
php获取是星期几的的一些常用姿势
2019/12/15 PHP
JavaScript 32位整型无符号操作示例
2013/12/08 Javascript
js浮点数保留两位小数点示例代码(四舍五入)
2013/12/26 Javascript
再探JavaScript作用域
2014/09/24 Javascript
深入理解javascript严格模式(Strict Mode)
2014/11/28 Javascript
JS实现为排序好的字符串找出重复行的方法
2016/03/02 Javascript
微信小程序-拍照或选择图片并上传文件
2017/01/06 Javascript
javascript作用域链与执行环境详解
2017/03/25 Javascript
js实现不提示直接关闭网页窗口
2017/03/30 Javascript
微信小程序 页面跳转事件绑定的实例详解
2017/09/20 Javascript
vue2.0 根据状态值进行样式的改变展示方法
2018/03/13 Javascript
JS实现自定义弹窗功能
2018/08/08 Javascript
Vue 全家桶实现移动端酷狗音乐功能
2018/11/16 Javascript
express+vue+mongodb+session 实现注册登录功能
2018/12/06 Javascript
python获取目录下所有文件的方法
2015/06/01 Python
Python多继承顺序实例分析
2018/05/26 Python
python for循环输入一个矩阵的实例
2018/11/14 Python
Python使用LDAP做用户认证的方法
2019/06/20 Python
matlab灰度图像调整及imadjust函数的用法详解
2020/02/27 Python
解决pytorch 交叉熵损失输出为负数的问题
2020/07/07 Python
django 模型字段设置默认值代码
2020/07/15 Python
Python排序函数的使用方法详解
2020/12/11 Python
详解px单位html5响应式方案
2018/03/08 HTML / CSS
建筑专业自荐信范文
2014/01/05 职场文书
告诉你怎样写创业计划书
2014/01/27 职场文书
广告业务员岗位职责
2014/02/06 职场文书
安全生产活动月方案
2014/03/09 职场文书
交通事故和解协议书
2014/09/25 职场文书
搞笑结婚保证书
2015/05/08 职场文书
结婚喜宴祝酒词
2015/08/10 职场文书