TypeScript Map对象详解
编程小白的TypeScript Map完全指南 – 用简单语言解释高级概念
Map是ES6引入的一种新的数据结构,用于存储键值对。在TypeScript中,你可以使用Map来存储任何类型的键和值,并保持插入顺序。
Map是什么?
可以把Map想象成一个更强大的键值对集合。它类似于JavaScript中的普通对象,但有几点关键区别:
- Map的键可以是任何类型(对象、函数、基本类型等)
- Map会记住键的插入顺序
- Map有方便的内置方法操作数据
- Map有size属性可直接获取元素数量
“name”
“Alice”
“age”
30
“isStudent”
false
在TypeScript中,你可以这样定义一个Map:
// 创建一个空Map
const myMap = new Map<string, number>();
// 创建带有初始值的Map
const userMap = new Map<string, any>([
["name", "张三"],
["age", 28],
["isAdmin", true]
]);
为什么需要Map?
你可能会问:JavaScript已经有普通对象了,为什么还需要Map?
下面展示了Map和普通对象的区别:
普通对象的问题
- 键只能是字符串或Symbol
- 不保留插入顺序
- 没有直接的size属性
- 原型上有默认属性,可能产生冲突
- 操作不够方便(如遍历)
Map的优势
- 键可以是任意类型(对象、函数等)
- 保持插入顺序
- 有size属性直接获取大小
- 干净的集合,没有原型属性
- 有丰富易用的方法
简单说:当你需要键不是字符串,或者需要保持插入顺序,或者需要更便捷的操作方法时,Map是更好的选择。
适用场景举例
- 需要对象作为键的缓存系统
- 需要保持元素顺序的集合
- 需要频繁增删键值对的场景
- 需要统计元素数量的情况
Map的基本操作
1. 创建Map
// 空Map
const map1 = new Map<string, number>();
// 带初始值
const map2 = new Map<string, string>([
['key1', 'value1'],
['key2', 'value2']
]);
// 任意类型键值
const map3 = new Map<any, any>();
map3.set(document.body, 'Body Element'); // DOM元素作为键
map3.set(() => {}, 'Function as key'); // 函数作为键
2. 添加/更新元素
const userMap = new Map<string, any>();
// 添加元素
userMap.set('name', '李四');
// 添加多个
userMap.set('age', 25)
.set('isAdmin', false); // 链式调用
// 更新元素
userMap.set('age', 26); // 覆盖已有值
3. 获取元素
console.log(userMap.get('name')); // 输出: 李四
console.log(userMap.get('nonExistent')); // 输出: undefined
4. 检查键是否存在
console.log(userMap.has('name')); // true
console.log(userMap.has('email')); // false
5. 删除元素
userMap.delete('isAdmin'); // 删除单个
userMap.clear(); // 删除所有元素
6. 获取大小
console.log(userMap.size); // 输出Map中的元素数量
遍历与转换
遍历Map的方法
const colorMap = new Map<string, string>([
['red', '#FF0000'],
['green', '#00FF00'],
['blue', '#0000FF']
]);
// 1. for...of遍历
for (let [key, value] of colorMap) {
console.log(`${key}: ${value}`);
}
// 2. forEach方法
colorMap.forEach((value, key) => {
console.log(`Key: ${key}, Value: ${value}`);
});
// 3. 遍历键
for (let key of colorMap.keys()) {
console.log(key);
}
// 4. 遍历值
for (let value of colorMap.values()) {
console.log(value);
}
Map与其他数据结构的转换
Map转数组:
// 转键值对数组
const arr = Array.from(colorMap); // [['red', '#FF0000'], ...]
// 转键数组
const keys = Array.from(colorMap.keys());
// 转值数组
const values = Array.from(colorMap.values());
数组转Map:
const arr = [['apple', 5], ['banana', 3]];
const fruitMap = new Map<string, number>(arr);
Map转对象:
const obj = Object.fromEntries(colorMap);
// { red: "#FF0000", green: "#00FF00", blue: "#0000FF" }
对象转Map:
const obj = {width: 100, height: 200};
const map = new Map<string, number>(Object.entries(obj));
高级特性与注意事项
1. 引用类型作为键
const objKey = { id: 1 };
const map = new Map<object, string>();
map.set(objKey, "对象作为键");
console.log(map.get(objKey)); // "对象作为键"
// 注意:必须是同一个对象引用
console.log(map.get({ id: 1 })); // undefined
对象作为键时,Map使用引用来判断键是否相同,而不是对象内容。
2. NaN作为键
const map = new Map<number, string>();
map.set(NaN, "这不是一个数字");
console.log(map.get(NaN)); // "这不是一个数字"
在Map中,NaN被视为相同的键,这点与普通对象不同。
3. Map的相等性
const map1 = new Map([['a', 1]]);
const map2 = new Map([['a', 1]]);
console.log(map1 === map2); // false
console.log(map1.get('a') === map2.get('a')); // true
即使两个Map有相同的内容,它们也是不同的对象。
4. Map与内存
当使用对象作为键时,即使对象在其他地方不再被使用,Map仍然保留对它的引用,可能导致内存泄漏。在不需要时记得删除:
let obj = { data: "重要数据" };
const map = new Map<object, string>();
map.set(obj, "关联值");
// 不再需要时
map.delete(obj);
obj = null; // 现在可以被垃圾回收
实际应用场景
1. 缓存系统
// 使用Map实现简单缓存
const cache = new Map<string, any>();
function getData(key: string) {
if (cache.has(key)) {
console.log('从缓存读取');
return cache.get(key);
}
console.log('从服务器获取');
const data = fetchDataFromServer(key); // 模拟获取数据
cache.set(key, data);
return data;
}
2. 数据分组
// 使用Map进行数据分组
const people = [
{ name: 'Alice', department: 'HR' },
{ name: 'Bob', department: 'IT' },
{ name: 'Charlie', department: 'IT' }
];
const byDept = new Map<string, any[]>();
people.forEach(person => {
const dept = person.department;
if (!byDept.has(dept)) {
byDept.set(dept, []);
}
byDept.get(dept)?.push(person);
});
console.log(byDept.get('IT')); // 输出IT部门的员工
3. 频率计数器
// 统计元素出现次数
function countFrequency(items: string[]) {
const frequencyMap = new Map<string, number>();
for (const item of items) {
frequencyMap.set(item, (frequencyMap.get(item) || 0) + 1);
}
return frequencyMap;
}
const fruits = ['apple', 'banana', 'apple', 'orange', 'banana', 'apple'];
console.log(countFrequency(fruits));
// Map(3) {'apple' => 3, 'banana' => 2, 'orange' => 1}