JavaScript Map
在JavaScript中,Map
是一种强大的集合类型,允许您存储键值对。与传统的对象不同,Map
提供了更多的灵活性和功能,特别是当处理复杂数据结构时。
Map vs 对象:为什么需要Map?
JavaScript对象({}
)一直被用作映射结构,但它有一些限制:
- 对象的键只能是字符串或Symbol
- 无法轻松获取对象的大小
- 对象没有内置的迭代能力
- 键的顺序不是保证的
Map
解决了这些问题,提供了一个更加完善的键值存储机制。
提示
Map
是ES6(ES2015)引入的特性,现代浏览器都支持,但在IE11等老浏览器中可能需要使用polyfill。
创建Map
创建一个新的Map很简单:
javascript
// 创建一个空Map
let myMap = new Map();
// 创建Map并初始化
let userInfo = new Map([
['name', 'Alice'],
['age', 25],
['isAdmin', false]
]);
console.log(userInfo);
// 输出: Map(3) { 'name' => 'Alice', 'age' => 25, 'isAdmin' => false }
Map的基本操作
添加和获取元素
javascript
// 创建空Map
let userPreferences = new Map();
// 添加元素
userPreferences.set('theme', 'dark');
userPreferences.set('fontSize', 16);
userPreferences.set('notifications', true);
// 获取元素
console.log(userPreferences.get('theme')); // 输出: dark
console.log(userPreferences.get('fontSize')); // 输出: 16
检查和删除元素
javascript
// 检查键是否存在
console.log(userPreferences.has('theme')); // 输出: true
console.log(userPreferences.has('language')); // 输出: false
// 删除元素
userPreferences.delete('notifications');
console.log(userPreferences.has('notifications')); // 输出: false
// 清空Map
userPreferences.clear();
console.log(userPreferences.size); // 输出: 0
Map的大小
javascript
let bookRatings = new Map([
['JavaScript高级程序设计', 4.7],
['你不知道的JavaScript', 4.8],
['JavaScript忍者秘籍', 4.5]
]);
console.log(bookRatings.size); // 输出: 3
Map的独特特性
任何类型的键
与对象不同,Map允许使用任何类型作为键,包括对象、函数甚至是其他Map:
javascript
let courseMap = new Map();
// 使用对象作为键
const courseA = { id: 101, name: 'JavaScript基础' };
const courseB = { id: 102, name: 'React入门' };
courseMap.set(courseA, { students: 30, price: 99 });
courseMap.set(courseB, { students: 25, price: 129 });
console.log(courseMap.get(courseA)); // 输出: { students: 30, price: 99 }
// 使用函数作为键
function sayHello() {
console.log('Hello!');
}
courseMap.set(sayHello, '这是一个问候函数');
console.log(courseMap.get(sayHello)); // 输出: '这是一个问候函数'
保持插入顺序
Map保持键的插入顺序,这使得迭代Map变得可预测:
javascript
let fruitInventory = new Map([
['apple', 50],
['banana', 30],
['orange', 25]
]);
// 迭代键值对
for (let [fruit, quantity] of fruitInventory) {
console.log(`${fruit}: ${quantity}`);
}
// 输出:
// apple: 50
// banana: 30
// orange: 25
Map的迭代方法
Map提供了多种迭代方法:
javascript
let scores = new Map([
['Alice', 95],
['Bob', 82],
['Charlie', 90]
]);
// 使用keys()获取所有键的迭代器
for (let student of scores.keys()) {
console.log(student);
}
// 输出: Alice, Bob, Charlie
// 使用values()获取所有值的迭代器
for (let score of scores.values()) {
console.log(score);
}
// 输出: 95, 82, 90
// 使用entries()获取所有[键,值]对的迭代器
for (let entry of scores.entries()) {
console.log(entry[0] + ': ' + entry[1]);
}
// 等同于使用解构
for (let [student, score] of scores) {
console.log(student + ': ' + score);
}
// 输出:
// Alice: 95
// Bob: 82
// Charlie: 90
// forEach方法
scores.forEach((score, student) => {
console.log(student + ' scored ' + score);
});
// 输出:
// Alice scored 95
// Bob scored 82
// Charlie scored 90
实际应用案例
1. 缓存系统
Map可以用来实现简单的缓存系统:
javascript
class SimpleCache {
constructor(maxAge = 3600000) { // 默认缓存1小时
this.cache = new Map();
this.maxAge = maxAge;
}
set(key, value) {
const entry = {
value,
expiry: Date.now() + this.maxAge
};
this.cache.set(key, entry);
}
get(key) {
const entry = this.cache.get(key);
if (!entry) return null;
if (Date.now() > entry.expiry) {
this.cache.delete(key);
return null;
}
return entry.value;
}
}
// 使用示例
const cache = new SimpleCache(10000); // 10秒缓存
cache.set('user123', { name: 'Alice', role: 'admin' });
// 10秒内
console.log(cache.get('user123')); // 输出: { name: 'Alice', role: 'admin' }
// 10秒后
setTimeout(() => {
console.log(cache.get('user123')); // 输出: null
}, 11000);
2. 频率计数器
Map非常适合计数和统计:
javascript
function countWordFrequency(text) {
// 将文本分割成单词并转为小写
const words = text.toLowerCase().match(/\w+/g) || [];
const frequency = new Map();
// 统计每个单词出现的次数
for (const word of words) {
const count = frequency.get(word) || 0;
frequency.set(word, count + 1);
}
return frequency;
}
const text = "Map 是 JavaScript 中的新特性,Map 提供了很多优点。";
const wordCount = countWordFrequency(text);
console.log(wordCount);
// 输出: Map(7) { 'map' => 2, 'javascript' => 1, ... }
// 找出出现最多的单词
let maxWord = '';
let maxCount = 0;
wordCount.forEach((count, word) => {
if (count > maxCount) {
maxCount = count;
maxWord = word;
}
});
console.log(`最常出现的单词是: "${maxWord}",出现了 ${maxCount} 次`);
// 输出: 最常出现的单词是: "map",出现了 2 次
3. 数据关联
Map可以用于关联不同类型的数据:
javascript
// 假设我们有一些DOM元素和与它们相关的数据
const domElementData = new Map();
document.querySelectorAll('.interactive-item').forEach(element => {
domElementData.set(element, {
clickCount: 0,
lastClicked: null,
metadata: {
id: element.id,
type: element.dataset.type
}
});
// 为元素添加点击处理程序
element.addEventListener('click', function() {
const data = domElementData.get(this);
data.clickCount++;
data.lastClicked = new Date();
console.log(`Element ${data.metadata.id} clicked ${data.clickCount} times`);
});
});
Map与Object的转换
从Object创建Map
javascript
const userObj = {
name: 'David',
email: 'david@example.com',
role: 'developer'
};
// 转换为Map
const userMap = new Map(Object.entries(userObj));
console.log(userMap);
// 输出: Map(3) { 'name' => 'David', 'email' => 'david@example.com', 'role' => 'developer' }
从Map创建Object
javascript
const settingsMap = new Map([
['darkMode', true],
['fontSize', 14],
['language', 'zh-CN']
]);
// 转换为对象
const settingsObj = Object.fromEntries(settingsMap);
console.log(settingsObj);
// 输出: { darkMode: true, fontSize: 14, language: 'zh-CN' }
警告
将Map转换为对象时,只有字符串和Symbol类型的键会被正确转换。如果Map包含对象键或其他非字符串类型的键,这些键会在转换过程中丢失或被转换为字符串。
WeakMap:与Map的"表亲"
JavaScript还提供了WeakMap
,它类似于Map,但有一些重要差异:
WeakMap
的键必须是对象(不能是原始值)- 当键对象没有其他引用时,它们会被垃圾回收
WeakMap
不可迭代,没有size属性和clear()方法
javascript
let weakMap = new WeakMap();
// 创建键对象
let keyObj1 = { id: 1 };
let keyObj2 = { id: 2 };
// 添加键值对
weakMap.set(keyObj1, "与键对象1关联的数据");
weakMap.set(keyObj2, "与键对象2关联的数据");
// 获取数据
console.log(weakMap.get(keyObj1)); // 输出: "与键对象1关联的数据"
// 让keyObj1被垃圾回收
keyObj1 = null;
// weakMap中对应的条目也会被自动回收
// 但我们无法通过API直接观察到这一点,因为WeakMap不可迭代
WeakMap
主要用于:
- 存储与DOM元素相关的私有数据
- 实现缓存,允许未使用的项目被垃圾回收
- 添加不会造成内存泄漏的元数据到对象上
性能考虑
对于大型数据集或频繁增删操作,Map通常比普通对象性能更好:
javascript
// 性能测试:Map vs Object
function performanceTest() {
const iterations = 1000000;
// 使用Map
console.time('Map');
const map = new Map();
for (let i = 0; i < iterations; i++) {
map.set('key' + i, i);
}
for (let i = 0; i < iterations; i++) {
map.get('key' + i);
}
console.timeEnd('Map');
// 使用Object
console.time('Object');
const obj = {};
for (let i = 0; i < iterations; i++) {
obj['key' + i] = i;
}
for (let i = 0; i < iterations; i++) {
const value = obj['key' + i];
}
console.timeEnd('Object');
}
// 在浏览器控制台运行此函数可以看到结果
// performanceTest();
总结
JavaScript的Map
是一个强大的数据结构,它提供了:
- 任何数据类型作为键
- 键值对的有序存储
- 简单的迭代功能
- 内置的size属性
- 方便的方法如set(), get(), has(), delete()等
相比传统对象,Map在处理复杂键值对关系、保持插入顺序和处理大量数据时有明显优势。无论是构建缓存系统、计数器还是关联数据,Map都是一个极佳的选择。
练习
- 创建一个函数,接受一个字符串,返回一个Map,其中包含每个字符及其出现次数。
- 实现一个简单的购物车系统,使用Map存储商品对象和数量。
- 使用Map创建一个学生成绩管理系统,能够添加、获取、删除学生成绩,并计算平均分。
- 比较使用Map和Object实现同一个功能的代码差异,分析各自的优缺点。
进一步学习资源
通过掌握Map,你会发现处理复杂数据结构变得更加直观和高效,这是现代JavaScript编程的重要技能之一。