JavaScript Map:优雅的键值对数据结构

文章目录

  • 前言
  • 一、Map是什么?
  • 二、Map基础用法
    • 2.1创建Map实例——new Map()
      • 2.1.1创建空的Map实例
      • 2.1.2创建有参的Map实例
    • 2.2设置键值对——set()
    • 2.3通过键获取值——get()
    • 2.4通过键验证是否有值——has()
    • 2.5删除指定键——delete()
    • 2.6清除所有键——clear()
    • 2.7键值对的获取——keys(),values(),entries()
    • 2.8迭代的两种方法
    • 2.9Map的复制
    • 2.10Map的合并
  • 三、总结Map的特点
    • 3.1无限制类型的键
    • 3.2有序性的键值对
    • 3.3 高效的增删查操作
    • 3.4 原生可迭代性
    • 3.5 安全性
  • 四、实践出真知:记录一次项目中使用Map来整理Excel导入的数据
  • 总结


前言

JavaScript 中,Map 是一种存储键值对的数据结构。比起Map,普通对象(Object)也常用来作为键值对存储数据。但Map在处理键值关系时更灵活、高效。本文会详细剖析Map数据结构,介绍何种时候,如何使用这个优雅的Map数据结构。


一、Map是什么?

Map 对象是键值对的集合。Map 中的一个键只能出现一次;它在 Map 的集合中是独一无二的。Map 对象按键值对迭代——一个 for…of 循环在每次迭代后会返回一个形式为 [key, value] 的数组。迭代按插入顺序进行,即键值对按 set() 方法首次插入到集合中的顺序(也就是说,当调用 set() 时,map 中没有具有相同值的键)进行迭代。

二、Map基础用法

2.1创建Map实例——new Map()

Map对象的构造函数结束有参和无参两种方式

通过new Map()返回一个Map实例。也可以利用 Map 构造函数和数组的 map 方法,快速将数组转换为 Map 结构,核心目的是通过键快速映射到原对象。

2.1.1创建空的Map实例

初始化空的Map实例

const sheetDataMap = new Map();

2.1.2创建有参的Map实例

构造函数接受一个可迭代对象(如数组),该对象的每个元素必须是键值对数组([key, value]

const sheetDataMap = new Map(
	sheetData.map(rowdata =>[`${rowdata.GisNo}`, rowdata])
);

2.2设置键值对——set()

同一个Map 对象中,可以添加了同类型的键值对,键包括字符串、数字、布尔值、对象和函数,值也是不同类型

实例对象的set()方法:在 Map 对象中设置与指定的键 key 关联的值,并返回 Map 对象。
其中set()方法的key可以是拼接复杂组键,可以是一个对象,甚至可以是一个函数。

const Name = "御道街",Address = "秦淮区";
sheetDataMap.set("742233fa", { "Name": Name, "Address": Address });
//复杂组键
sheetDataMap.set(
	`742233fa:${type}`,{ "Name": Name, "Address": Address }
);
//对象键
const objKey = { key: "742233fa" };
sheetDataMap.set(objKey,{ "Name": Name, "Address": Address });
//函数键
const funcKey = () => {
	return { key: "742233fa" }
};
sheetDataMap.set(funcKey, { "Name": Name, "Address": Address });

与键 key 关联的值同样可以设置多种类型。类似键的声明。

2.3通过键获取值——get()

实例对象的get()方法:返回与指定的键 key 关联的值,若不存在关联的值,则返回 undefined。

//返回"{Name:"御道街",Address:"秦淮区"}"
return sheetDataMap.get('742233fa');

2.4通过键验证是否有值——has()

实例对象的has()方法:返回一个布尔值,用来表明 Map 对象中是否存在与指定的键 key 关联的值。

//返回true
return sheetDataMap.has('742233fa');

2.5删除指定键——delete()

实例对象的delete()方法:返回一个布尔值,移除 Map 对象中指定的键值对,如果键值对存在并成功被移除,返回 true,否则返回 false。调用 delete 后再调用 map.has(key) 将返回 false。

//返回true
return sheetDataMap.delete('742233fa');

2.6清除所有键——clear()

实例对象的clear()方法:移除 Map 对象中所有的键值对。

return sheetDataMap.clear();

2.7键值对的获取——keys(),values(),entries()

keys():返回一个新的迭代器对象,其包含 Map 对象中所有元素的键,以插入顺序排列。
values():返回一个新的迭代对象,其中包含 Map 对象中所有的值,并以插入 Map 对象的顺序排列。
entries():返回一个新的迭代器对象,其包含 Map 对象中所有键值对 [key, value] 二元数组,以插入顺序排列。
示例囊括在迭代方法示例里

2.8迭代的两种方法

Map 可以使用 for…of 循环来实现迭代,也可以通过 forEach() 方法迭代:
for...of

sheetDataMap.set("742233fa",{"Name":"御道街","Address":"秦淮区"});
sheetDataMap.set("72106d24",{"Name":"后宰门","Address":"玄武区"});
for (const [key, value] of sheetDataMap) {
  console.log(`${key} = ${value}`);
}
// "742233fa" = {"Name":"御道街","Address":"秦淮区"}
// "72106d24"= {"Name":"后宰门","Address":"玄武区"}

//遍历所有的key
for (const key of sheetDataMap.keys()) {
  console.log(key);
}
// "742233fa" 
// "72106d24"

//遍历所有的值
for (const value of sheetDataMap.values()) {
  console.log(value);
}
// {"Name":"御道街","Address":"秦淮区"}
// {"Name":"后宰门","Address":"玄武区"}

//遍历所有的键值对
for (const [key, value] of sheetDataMap.entries()) {
  console.log(`${key} = ${value}`);
}
// "742233fa" = {"Name":"御道街","Address":"秦淮区"}
// "72106d24"= {"Name":"后宰门","Address":"玄武区"}

forEach()

sheetDataMap.forEach((value, key) => {
  console.log(`${key} = ${value}`);
});
// "742233fa" = {Name:"御道街","Address":"秦淮区"}
// "72106d24"= {Name:"后宰门","Address":"玄武区"}

2.9Map的复制

Map 能像数组一样被复制,通过使用 Map 构造函数传入已有的 Map 实例来实现浅复制;

originalMap 与cloneMap 是两个不同的实例,不为同一个对象的引用
originalMap 和 cloneMap 里的 “742233fa” 键所对应的值实际上指向的是内存中的同一个对象

const originalMap = new Map([["742233fa",{"Name":"御道街","Address":"秦淮区"}]]);
const cloneMap = new Map(originalMap);
console.log(cloneMap.get("742233fa")); // {"Name":"御道街","Address":"秦淮区"}
console.log(originalMap === cloneMap ); // false. 两个不同的实例,浅引用
console.log(originalMap.get("742233fa") === cloneMap.get("742233fa")); // true. 对应的值实际上指向的是内存中的同一个对象。浅拷贝

2.10Map的合并

Map 之间以及 Map 和数组之间可以进行合并操作;

sheetDataMap.set("742233fa",{"Name":"御道街","Address":"秦淮区"});
sheetDataMap.set("72106d24",{"Name":"后宰门","Address":"玄武区"});
const firstMap = new Map([["742233fa",{"Name":"御道街","Address":"秦淮区"}],
			["6b7d7624",{"Name":"御道街","Address":"秦淮区"}]]);
const secondMap = new Map([["72106d24",{"Name":"后宰门","Address":"玄武区"}]]);
// Map 对象同数组进行合并时,如果有重复的键值,则后面的会覆盖前面的。
const mergedMap = new Map([...firstMap , ...secondMap, ["6b7d7624",{"Name":"双龙大道","Address":"雨花台区"}]]);
//mergedMap "6b7d7624"key对应的值便是{"Name":"双龙大道","Address":"雨花台区"}

三、总结Map的特点

3.1无限制类型的键

普通对象(Object)的键只能是字符串(包括 Symbol),而 Map 结构的键可以是任意类型(原始值如数字、字符串、布尔值,或引用值如对象、函数、数组等)。如上示例里的使用

3.2有序性的键值对

Map 会严格按照键的插入顺序存储键值对,Map 中的键以简单、直接的方式排序。Map 对象按照插入的顺序迭代条目、键和值,遍历时也会按此顺序输出。

3.3 高效的增删查操作

在涉及频繁添加和删除键值对的场景中表现非常好。平均情况下的时间复杂度为 O (1)。

3.4 原生可迭代性

Map 是可迭代对象,所以它可以直接迭代。

3.5 安全性

Map 默认不包含任何键。它只包含显式存入的键值对。Object 有原型,因此它包含默认的键,如果不小心的话,它们可能会与你自己的键相冲突。在 Object 上设置用户提供的键值对可能会允许攻击者覆盖对象的原型,这可能会导致对象注入攻击。所以Map 相对安全一些。

四、实践出真知:记录一次项目中使用Map来整理Excel导入的数据

整理导入进来的Excel数据时,有时候会高频次的修改来自sheet里的数据。传统的遍历查找效率十分低下。考虑到Map数据结构的特性,我们可以构建键值对,将sheet数据转换成Map结构。
复合键一定要能做到唯一性,不然会存在丢失数据的情况。

//构建Map实例
const sheetDataMap = new Map(sheetData.map(rowdata => [`${rowdata.GisNo}-${rowdata.MeterAdd}`, rowdata]));
// 对比两组数据。遍历处理后的数据数组,为每个sheet数据行设置PID 
for (var i = 0; i < res.data.length; i++) {
    // 生成复合键:GisNo + MeterAdd
    const key = `${update.GisNo}-${update.MeterAdd}`;
    if (sheetDataMap.has(key)) {
        const _rowdata = sheetDataMap.get(key);
        _rowdata.PID = res.data[i].PID ;
    }
}

//Map实例转数组
let sheetData = Array.from(sheetDataMap.values());

总结

Map 是 JavaScript 中一种优雅的键值对数据结构。核心优势在于任意类型的键、有序性和原生的迭代与操作方法。当需要处理复杂键、动态数据或高频操作时,Map能使代码运行效率变高。

你可能感兴趣的:(JavaScript,javascript,前端,数据结构)