Map 数据结构:存储键值对(任意类型键) (ES6+)
引言
大家好,欢迎来到今天的讲座!今天我们要聊聊 JavaScript 中的 Map 数据结构。如果你已经熟悉了对象(Object),那么 Map 可能会让你眼前一亮。它不仅提供了更强大的功能,还能解决一些对象无法处理的问题。别担心,我们会用轻松诙谐的方式带你走进 Map 的世界,让你在欢笑中掌握这个强大的工具。
什么是 Map?
在 ES6 之前,JavaScript 中最常用的键值对存储方式是对象(Object)。对象的键只能是字符串或符号(Symbol),这限制了它的灵活性。而 Map 是一种新的数据结构,允许你使用 任意类型的键,包括数字、字符串、对象、甚至函数!这使得 Map 在某些场景下比对象更加灵活和强大。
Map 的基本特点
- 任意类型的键:
Map的键可以是任何类型的数据,不仅仅是字符串或符号。 - 保持插入顺序:
Map会按照插入的顺序保存键值对,而对象的键是无序的。 - 更好的性能:对于频繁的增删操作,
Map的性能通常优于对象。 - 内置方法:
Map提供了许多实用的方法,如set()、get()、has()、delete()和clear()等。
创建一个 Map
创建 Map 非常简单,只需要调用 new Map() 即可。你可以通过传递一个数组来初始化 Map,其中每个元素都是一个包含两个值的数组,分别表示键和值。
// 创建一个空的 Map
const myMap = new Map();
// 通过数组初始化 Map
const fruitPrices = new Map([
['apple', 1.2],
['banana', 0.8],
['orange', 1.5]
]);
console.log(fruitPrices); // Map(3) { 'apple' => 1.2, 'banana' => 0.8, 'orange' => 1.5 }
基本操作
1. 设置键值对 (set()) 和 获取值 (get())
set() 方法用于向 Map 中添加或更新键值对,而 get() 方法用于根据键获取对应的值。
const prices = new Map();
// 设置键值对
prices.set('apple', 1.2);
prices.set('banana', 0.8);
// 获取值
console.log(prices.get('apple')); // 1.2
console.log(prices.get('banana')); // 0.8
2. 检查键是否存在 (has())
有时候我们想知道某个键是否存在于 Map 中,这时可以使用 has() 方法。
console.log(prices.has('apple')); // true
console.log(prices.has('grape')); // false
3. 删除键值对 (delete())
如果你想删除某个键值对,可以使用 delete() 方法。
prices.delete('banana');
console.log(prices.has('banana')); // false
4. 清空 Map (clear())
如果你想清空整个 Map,可以使用 clear() 方法。
prices.clear();
console.log(prices.size); // 0
迭代 Map
Map 提供了多种迭代方式,方便我们在遍历键值对时进行操作。常见的迭代方法有 keys()、values() 和 entries()。
1. 遍历所有键 (keys())
keys() 方法返回一个迭代器,遍历 Map 中的所有键。
const fruitPrices = new Map([
['apple', 1.2],
['banana', 0.8],
['orange', 1.5]
]);
for (const key of fruitPrices.keys()) {
console.log(key); // apple, banana, orange
}
2. 遍历所有值 (values())
values() 方法返回一个迭代器,遍历 Map 中的所有值。
for (const value of fruitPrices.values()) {
console.log(value); // 1.2, 0.8, 1.5
}
3. 遍历所有键值对 (entries())
entries() 方法返回一个迭代器,遍历 Map 中的所有键值对。每个键值对是一个包含两个元素的数组,分别是键和值。
for (const [key, value] of fruitPrices.entries()) {
console.log(`${key}: $${value}`); // apple: $1.2, banana: $0.8, orange: $1.5
}
4. 使用 forEach() 遍历
Map 还提供了一个 forEach() 方法,可以直接遍历所有的键值对,并执行回调函数。
fruitPrices.forEach((value, key) => {
console.log(`${key}: $${value}`);
});
Map 与 Object 的区别
虽然 Map 和对象都可以存储键值对,但它们之间有一些重要的区别。下面是一个简单的对比表格:
| 特性 | Map |
Object |
|---|---|---|
| 键的类型 | 任意类型(包括对象、函数等) | 只能是字符串或符号(Symbol) |
| 插入顺序 | 保持插入顺序 | 不保证顺序 |
| 性能 | 对于频繁的增删操作性能更好 | 对于小规模数据集性能相似 |
| 内置方法 | 提供丰富的内置方法(set()、get()等) |
内置方法较少,依赖 Object API |
| 默认属性 | 没有默认属性 | 有默认属性(如 __proto__) |
从表格中可以看出,Map 在灵活性和性能方面都优于对象,尤其是在需要使用非字符串键或保持插入顺序的场景下。
实际应用场景
1. 缓存机制
Map 的高效增删特性和保持插入顺序的特点,使得它非常适合用于缓存机制。例如,我们可以使用 Map 来实现一个简单的 LRU(Least Recently Used)缓存。
class LRUCache {
constructor(capacity) {
this.capacity = capacity;
this.cache = new Map();
}
get(key) {
if (!this.cache.has(key)) return -1;
// 将访问的键移到最后,表示最近使用
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
put(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
} else if (this.cache.size >= this.capacity) {
// 移除最早使用的键
this.cache.delete(this.cache.keys().next().value);
}
this.cache.set(key, value);
}
}
const cache = new LRUCache(2);
cache.put('a', 1);
cache.put('b', 2);
console.log(cache.get('a')); // 1
cache.put('c', 3);
console.log(cache.get('b')); // -1 (已移除)
2. 计数器
Map 也可以用于实现计数器,统计某个集合中元素出现的次数。相比于对象,Map 可以直接使用任意类型的键,而不需要将键转换为字符串。
function countOccurrences(arr) {
const counter = new Map();
for (const item of arr) {
counter.set(item, (counter.get(item) || 0) + 1);
}
return counter;
}
const numbers = [1, 2, 3, 2, 1, 3, 4, 1];
const occurrences = countOccurrences(numbers);
console.log(occurrences); // Map(4) { 1 => 3, 2 => 2, 3 => 2, 4 => 1 }
结语
好了,今天的讲座到这里就结束了!希望你对 Map 有了更深入的了解。Map 是一个非常强大且灵活的数据结构,能够帮助你在各种场景下更高效地处理键值对。下次当你遇到需要使用非字符串键或保持插入顺序的需求时,不妨考虑一下 Map 吧!
如果你有任何问题或想法,欢迎在评论区留言讨论。期待下次再见! ?
参考资料:
- MDN Web Docs: The
Mapobject is a simple key/value map. - ECMAScript 2015 (ES6) specification: The
Mapobject holds key-value pairs and remembers the original insertion order of the keys.