Appearance
TypeScript Map 对象
在 TypeScript 中,Map 是一种特殊的数据结构,用于存储键值对,其中键可以是任何类型(包括对象、数组、函数等)。Map 是 ES6 引入的新特性,它提供了比普通对象更灵活的键值存储方式。本文将详细介绍 TypeScript 中的 Map 对象。
1. 基本用法
创建 Map
typescript
// 创建空 Map
let map1: Map<string, number> = new Map();
// 创建带有初始值的 Map
let map2: Map<string, number> = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
// 使用对象作为键
let objKey = { id: 1 };
let map3: Map<object, string> = new Map([
[objKey, "value"]
]);
// 使用数组作为键
let arrKey = [1, 2, 3];
let map4: Map<number[], string> = new Map([
[arrKey, "value"]
]);
// 使用函数作为键
let funcKey = () => console.log("hello");
let map5: Map<Function, string> = new Map([
[funcKey, "value"]
]);添加和获取元素
typescript
let map: Map<string, number> = new Map();
// 添加元素
map.set("a", 1);
map.set("b", 2);
map.set("c", 3);
// 获取元素
console.log(map.get("a")); // 输出:1
console.log(map.get("b")); // 输出:2
console.log(map.get("c")); // 输出:3
console.log(map.get("d")); // 输出:undefined检查元素是否存在
typescript
let map: Map<string, number> = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
console.log(map.has("a")); // 输出:true
console.log(map.has("d")); // 输出:false删除元素
typescript
let map: Map<string, number> = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
// 删除元素
map.delete("b");
console.log(map.has("b")); // 输出:false
// 清除所有元素
map.clear();
console.log(map.size); // 输出:0获取 Map 大小
typescript
let map: Map<string, number> = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
console.log(map.size); // 输出:32. 类型注解
基本类型键值对
typescript
// 字符串键,数字值
let map1: Map<string, number> = new Map();
// 数字键,字符串值
let map2: Map<number, string> = new Map();
// 布尔键,对象值
let map3: Map<boolean, object> = new Map();复杂类型键值对
typescript
// 对象键,字符串值
let map1: Map<object, string> = new Map();
// 数组键,数字值
let map2: Map<number[], number> = new Map();
// 函数键,布尔值
let map3: Map<Function, boolean> = new Map();联合类型键值对
typescript
// 字符串或数字键,字符串值
let map1: Map<string | number, string> = new Map();
// 字符串键,数字或布尔值
let map2: Map<string, number | boolean> = new Map();泛型类型
typescript
// 泛型 Map 类型
interface User {
id: number;
name: string;
}
let users: Map<number, User> = new Map([
[1, { id: 1, name: "John" }],
[2, { id: 2, name: "Jane" }]
]);3. Map 方法
set(key, value)
添加或更新键值对,返回 Map 实例。
typescript
let map: Map<string, number> = new Map();
map.set("a", 1).set("b", 2).set("c", 3);
console.log(map.get("a")); // 输出:1
console.log(map.get("b")); // 输出:2
console.log(map.get("c")); // 输出:3
// 更新值
map.set("a", 10);
console.log(map.get("a")); // 输出:10get(key)
获取指定键的值,如果不存在则返回 undefined。
typescript
let map: Map<string, number> = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
console.log(map.get("a")); // 输出:1
console.log(map.get("d")); // 输出:undefinedhas(key)
检查指定键是否存在,返回布尔值。
typescript
let map: Map<string, number> = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
console.log(map.has("a")); // 输出:true
console.log(map.has("d")); // 输出:falsedelete(key)
删除指定键的键值对,返回布尔值表示是否删除成功。
typescript
let map: Map<string, number> = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
console.log(map.delete("b")); // 输出:true
console.log(map.has("b")); // 输出:false
console.log(map.delete("d")); // 输出:falseclear()
清除所有键值对,无返回值。
typescript
let map: Map<string, number> = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
map.clear();
console.log(map.size); // 输出:0
console.log(map.has("a")); // 输出:falsekeys()
返回一个包含所有键的迭代器。
typescript
let map: Map<string, number> = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
for (const key of map.keys()) {
console.log(key); // 输出:a, b, c
}
// 转换为数组
let keys: string[] = Array.from(map.keys());
console.log(keys); // 输出:["a", "b", "c"]values()
返回一个包含所有值的迭代器。
typescript
let map: Map<string, number> = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
for (const value of map.values()) {
console.log(value); // 输出:1, 2, 3
}
// 转换为数组
let values: number[] = Array.from(map.values());
console.log(values); // 输出:[1, 2, 3]entries()
返回一个包含所有键值对的迭代器。
typescript
let map: Map<string, number> = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
for (const [key, value] of map.entries()) {
console.log(`${key}: ${value}`); // 输出:a: 1, b: 2, c: 3
}
// 转换为数组
let entries: [string, number][] = Array.from(map.entries());
console.log(entries); // 输出:[["a", 1], ["b", 2], ["c", 3]]forEach(callback, thisArg?)
遍历 Map 中的每个键值对,执行回调函数。
typescript
let map: Map<string, number> = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
map.forEach((value, key, map) => {
console.log(`${key}: ${value}`); // 输出:a: 1, b: 2, c: 3
});
// 使用 thisArg
const obj = { prefix: "Value: " };
map.forEach(function(value, key) {
console.log(`${this.prefix}${key}: ${value}`); // 输出:Value: a: 1, Value: b: 2, Value: c: 3
}, obj);4. 类型守卫
在 TypeScript 中,可以使用类型守卫来检查一个值是否是 Map 类型。
instanceof 类型守卫
typescript
function processValue(value: any) {
if (value instanceof Map) {
// 在这个分支中,TypeScript 知道 value 是 Map 类型
console.log(`Map size: ${value.size}`);
value.forEach((val, key) => console.log(`${key}: ${val}`));
} else {
console.log(`Not a Map: ${value}`);
}
}
processValue(new Map([["a", 1], ["b", 2]])); // 输出:Map size: 2 a: 1 b: 2
processValue({ a: 1, b: 2 }); // 输出:Not a Map: [object Object]
processValue([1, 2, 3]); // 输出:Not a Map: 1,2,3自定义类型守卫
typescript
function isMap(value: any): value is Map<any, any> {
return value instanceof Map;
}
function isStringNumberMap(value: any): value is Map<string, number> {
if (!(value instanceof Map)) return false;
for (const [key, val] of value.entries()) {
if (typeof key !== "string" || typeof val !== "number") {
return false;
}
}
return true;
}
function processValue(value: any) {
if (isStringNumberMap(value)) {
// 在这个分支中,TypeScript 知道 value 是 Map<string, number> 类型
let sum = 0;
value.forEach((val) => sum += val);
console.log(`Sum of values: ${sum}`);
} else {
console.log(`Not a Map<string, number>: ${value}`);
}
}
processValue(new Map([["a", 1], ["b", 2]])); // 输出:Sum of values: 3
processValue(new Map([["a", "1"], ["b", "2"]])); // 输出:Not a Map<string, number>: [object Map]
processValue({ a: 1, b: 2 }); // 输出:Not a Map<string, number>: [object Object]5. Map 操作的最佳实践
1. 使用类型注解
为 Map 添加类型注解可以提高代码的可读性和类型安全性。
typescript
// 推荐
let map: Map<string, number> = new Map();
// 不推荐
let map = new Map(); // 类型推断为 Map<unknown, unknown>2. 使用 const 声明只读 Map
对于不需要修改的 Map,使用 const 声明可以防止意外修改。
typescript
// 推荐
const map: Map<string, number> = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
// 不推荐
let map: Map<string, number> = new Map([
["a", 1],
["b", 2],
["c", 3]
]); // 可能被意外修改3. 优先使用 Map 而非对象
当键不是字符串或需要保持插入顺序时,优先使用 Map。
typescript
// 推荐:使用 Map 存储非字符串键
const objKey = { id: 1 };
const map: Map<object, string> = new Map([
[objKey, "value"]
]);
// 不推荐:使用对象存储非字符串键(会被转换为字符串)
const objKey = { id: 1 };
const obj: Record<string, string> = {};
obj[objKey] = "value"; // 键会被转换为 "[object Object]"
console.log(obj[objKey]); // 输出:undefined
console.log(obj["[object Object]"]); // 输出:value4. 注意 Map 的键比较
Map 使用严格相等(===)来比较键,包括对象引用。
typescript
const map: Map<object, string> = new Map();
const obj1 = { id: 1 };
const obj2 = { id: 1 };
map.set(obj1, "value1");
console.log(map.get(obj1)); // 输出:value1
console.log(map.get(obj2)); // 输出:undefined(obj1 和 obj2 是不同的引用)5. 合理使用 Map 方法
了解 Map 方法的使用场景,选择合适的方法。
typescript
// 推荐:使用 forEach 遍历 Map
const map: Map<string, number> = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
map.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// 推荐:使用 entries() 遍历 Map
for (const [key, value] of map.entries()) {
console.log(`${key}: ${value}`);
}6. 注意 Map 的性能
对于频繁查找的场景,Map 的性能可能不如对象,因为对象的属性访问是直接的,而 Map 需要通过方法调用。
typescript
// 对于频繁查找的场景,对象可能更高效
const obj: Record<string, number> = {
a: 1,
b: 2,
c: 3
};
console.log(obj.a); // 直接访问,性能更好
// 对于需要保持插入顺序或非字符串键的场景,Map 更合适
const map: Map<string, number> = new Map([
["a", 1],
["b", 2],
["c", 3]
]);
console.log(map.get("a")); // 方法调用,性能略差6. 常见错误
1. 类型不匹配
在 TypeScript 中,Map 的键和值类型必须与类型注解匹配。
typescript
// 错误:类型不匹配
let map: Map<string, number> = new Map([
["a", 1],
["b", "2"] // 类型 'string' 不能赋值给类型 'number'
]);
// 正确
let map: Map<string, number> = new Map([
["a", 1],
["b", 2]
]);
let map2: Map<string, number | string> = new Map([
["a", 1],
["b", "2"]
]);2. 键比较错误
Map 使用严格相等(===)来比较键,包括对象引用。
typescript
const map: Map<object, string> = new Map();
const obj1 = { id: 1 };
const obj2 = { id: 1 };
map.set(obj1, "value");
console.log(map.get(obj2)); // 输出:undefined(错误:期望输出 value)3. 忘记使用 Map 方法
使用 Map 时,必须使用 Map 提供的方法来操作,而不是像对象一样直接访问。
typescript
const map: Map<string, number> = new Map();
// 错误:直接赋值(不会添加到 Map 中)
map["a"] = 1;
console.log(map.get("a")); // 输出:undefined
// 正确:使用 set 方法
map.set("a", 1);
console.log(map.get("a")); // 输出:14. 混淆 Map 和对象
Map 和对象有不同的使用方式,混淆它们会导致错误。
typescript
// 错误:使用对象的方式访问 Map
const map: Map<string, number> = new Map([
["a", 1],
["b", 2]
]);
console.log(map.a); // 输出:undefined
// 正确:使用 get 方法
console.log(map.get("a")); // 输出:15. 错误使用 Map 的 size 属性
Map 的 size 是一个属性,不是方法,不需要括号。
typescript
const map: Map<string, number> = new Map([
["a", 1],
["b", 2]
]);
// 错误:使用方法调用
console.log(map.size()); // 运行时错误:map.size is not a function
// 正确:直接访问属性
console.log(map.size); // 输出:27. Map 与对象的比较
1. 键的类型
- Map:键可以是任何类型(字符串、数字、对象、数组、函数等)。
- 对象:键只能是字符串、数字或 Symbol。
2. 键的顺序
- Map:保持插入顺序,遍历顺序与插入顺序一致。
- 对象:在 ES6 之前不保证顺序,ES6 之后基本保持插入顺序,但数字键会被排序。
3. 大小
- Map:有
size属性,可以直接获取键值对数量。 - 对象:需要手动计算,使用
Object.keys(obj).length。
4. 迭代
- Map:可以直接使用
for...of循环或forEach方法遍历。 - 对象:需要使用
Object.keys(),Object.values(),Object.entries()等方法。
5. 性能
- Map:对于频繁添加和删除键值对的场景,性能更好。
- 对象:对于频繁查找的场景,性能可能更好。
6. 序列化
- Map:默认情况下不能直接 JSON 序列化,需要手动转换。
- 对象:可以直接使用
JSON.stringify()序列化。
8. 实际应用场景
1. 存储用户数据
typescript
interface User {
id: number;
name: string;
email: string;
}
const users: Map<number, User> = new Map([
[1, { id: 1, name: "John", email: "john@example.com" }],
[2, { id: 2, name: "Jane", email: "jane@example.com" }],
[3, { id: 3, name: "Bob", email: "bob@example.com" }]
]);
// 根据 ID 获取用户
function getUserById(id: number): User | undefined {
return users.get(id);
}
// 添加新用户
function addUser(user: User): void {
users.set(user.id, user);
}
// 删除用户
function deleteUser(id: number): boolean {
return users.delete(id);
}2. 缓存数据
typescript
class Cache<K, V> {
private map: Map<K, V>;
private maxSize: number;
constructor(maxSize: number = 100) {
this.map = new Map();
this.maxSize = maxSize;
}
get(key: K): V | undefined {
return this.map.get(key);
}
set(key: K, value: V): void {
// 如果缓存已满,删除最早的键值对
if (this.map.size >= this.maxSize) {
const firstKey = this.map.keys().next().value;
this.map.delete(firstKey);
}
this.map.set(key, value);
}
has(key: K): boolean {
return this.map.has(key);
}
delete(key: K): boolean {
return this.map.delete(key);
}
clear(): void {
this.map.clear();
}
get size(): number {
return this.map.size;
}
}
// 使用示例
const cache = new Cache<string, number>(3);
cache.set("a", 1);
cache.set("b", 2);
cache.set("c", 3);
console.log(cache.get("a")); // 输出:1
cache.set("d", 4); // 缓存已满,删除最早的 "a"
console.log(cache.get("a")); // 输出:undefined
console.log(cache.get("b")); // 输出:23. 事件管理
typescript
type EventHandler = (...args: any[]) => void;
class EventEmitter {
private events: Map<string, Set<EventHandler>>;
constructor() {
this.events = new Map();
}
on(event: string, handler: EventHandler): void {
if (!this.events.has(event)) {
this.events.set(event, new Set());
}
this.events.get(event)!.add(handler);
}
off(event: string, handler: EventHandler): void {
if (this.events.has(event)) {
this.events.get(event)!.delete(handler);
if (this.events.get(event)!.size === 0) {
this.events.delete(event);
}
}
}
emit(event: string, ...args: any[]): void {
if (this.events.has(event)) {
this.events.get(event)!.forEach(handler => handler(...args));
}
}
clear(event?: string): void {
if (event) {
this.events.delete(event);
} else {
this.events.clear();
}
}
get eventNames(): string[] {
return Array.from(this.events.keys());
}
}
// 使用示例
const emitter = new EventEmitter();
const handler1 = (message: string) => console.log(`Handler 1: ${message}`);
const handler2 = (message: string) => console.log(`Handler 2: ${message}`);
emitter.on("message", handler1);
emitter.on("message", handler2);
emitter.emit("message", "Hello, world!");
// 输出:
// Handler 1: Hello, world!
// Handler 2: Hello, world!
emitter.off("message", handler1);
emitter.emit("message", "Hello again!");
// 输出:
// Handler 2: Hello again!总结
TypeScript 中的 Map 对象是一种强大的数据结构,用于存储键值对,其中键可以是任何类型。它包括:
- 基本用法:创建 Map,添加和获取元素,检查元素是否存在,删除元素,获取 Map 大小
- 类型注解:基本类型键值对,复杂类型键值对,联合类型键值对,泛型类型
- Map 方法:set, get, has, delete, clear, keys, values, entries, forEach
- 类型守卫:使用 instanceof 和自定义类型守卫检查 Map 类型
- 最佳实践:使用类型注解,使用 const 声明只读 Map,优先使用 Map 而非对象,注意 Map 的键比较,合理使用 Map 方法,注意 Map 的性能
- 常见错误:类型不匹配,键比较错误,忘记使用 Map 方法,混淆 Map 和对象,错误使用 Map 的 size 属性
- Map 与对象的比较:键的类型,键的顺序,大小,迭代,性能,序列化
- 实际应用场景:存储用户数据,缓存数据,事件管理
通过合理使用 Map 对象,你可以在 TypeScript 中更灵活地处理键值对数据,特别是当键不是字符串或需要保持插入顺序时。