目录
一、Map(映射)
Map 是 ES6 引入的一种新的数据结构,它类似于对象,也是键值对的集合,但 Map 的键可以是任意类型的值(对象、原始值等),而不仅仅是字符串或 Symbol。
1.基本用法
// 创建一个空Map
const map = new Map();
// 创建时初始化
const initializedMap = new Map([
['key1', 'value1'],
['key2', 'value2']
]);
// 从对象创建Map
const obj = { a: 1, b: 2 };
const mapFromObject = new Map(Object.entries(obj));
2.常用方法和属性
-
set(key, value) - 添加或更新键值对
map.set('name', 'Alice'); map.set(1, 'number key'); map.set({}, 'object key'); map.set(null, 'null key'); // 甚至可以使用null或undefined作为键
-
get(key) - 获取键对应的值
const name = map.get('name'); // 'Alice' const unknown = map.get('non-existent'); // undefined
-
has(key) - 检查是否存在某个键
const hasName = map.has('name'); // true
-
delete(key) - 删除某个键值对
const deleted = map.delete('name'); // 返回布尔值表示是否删除成功
-
clear() - 清空所有键值对
map.clear();
-
size - 获取键值对数量
const size = map.size;
3.遍历方法
-
keys() - 返回所有键的迭代器
for (const key of map.keys()) { console.log(key); } // 或者转换为数组 const keysArray = Array.from(map.keys());
-
values() - 返回所有值的迭代器
for (const value of map.values()) { console.log(value); }
-
entries() - 返回所有键值对的迭代器
for (const [key, value] of map.entries()) { console.log(key, value); } // 可以简写为 for (const [key, value] of map) { console.log(key, value); }
-
forEach() - 遍历所有键值对
map.forEach((value, key) => { console.log(key, value); // 注意:value在前,key在后,与数组的forEach不同 });
4.TypeScript 中的类型定义
// 明确指定键和值的类型
const typedMap = new Map<string, number>();
typedMap.set('age', 25); // 正确
typedMap.set('name', 'Alice'); // 错误,值应该是number类型
// 复杂类型示例
interface User {
id: number;
name: string;
}
const userMap = new Map<number, User>();
userMap.set(1, { id: 1, name: 'Alice' });
// 从现有类型派生Map类型
type UserMap = Map<number, User>;
5.Map 与普通对象的区别
- Map 的键可以是任意类型,而对象的键只能是字符串或 Symbol
- Map 的大小可以通过 size 属性直接获取,而对象需要手动计算
- Map 在频繁增删键值对的场景下性能更好
- Map 会维护键值对的插入顺序
- Map 可以直接遍历,而对象需要先获取键数组
6.使用场景
- 需要键不是字符串/数字/Symbol的情况
- 需要频繁增删键值对的场景
- 需要维护插入顺序的场景
- 需要更便捷的遍历和大小获取的场景
二、Set(集合)
Set 是 ES6 引入的另一种数据结构,它类似于数组,但成员的值都是唯一的,没有重复的值。
1.基本用法
// 创建一个空Set
const set = new Set();
// 创建时初始化
const initializedSet = new Set([1, 2, 3, 4]);
// 从类数组对象创建
const arrayLike = { 0: 'a', 1: 'b', length: 2 };
const setFromArrayLike = new Set(Array.from(arrayLike));
// 字符串会被拆分为字符
const charSet = new Set('hello'); // Set {'h', 'e', 'l', 'o'}
2.常用方法和属性
-
add(value) - 添加值,返回Set本身(可以链式调用)
set.add(1).add(2).add(3); set.add(NaN).add(NaN); // Set中NaN等于自身,只会保留一个
-
has(value) - 检查是否存在某个值
const hasTwo = set.has(2); // true set.has(NaN); // 可以正确检测NaN
-
delete(value) - 删除某个值
set.delete(2);
-
clear() - 清空所有值
set.clear();
-
size - 获取值的数量
const size = set.size;
3.遍历方法
-
values() - 返回所有值的迭代器
for (const value of set.values()) { console.log(value); }
-
keys() - 与values()相同,为了与Map兼容
for (const key of set.keys()) { console.log(key); }
-
entries() - 返回[value, value]形式的迭代器,为了与Map兼容
for (const [key, value] of set.entries()) { console.log(key, value); // 两者相同 }
-
forEach() - 遍历所有值
set.forEach((value) => { console.log(value); // 注意:参数设计为与Map的forEach一致 });
4.TypeScript 中的类型定义
// 明确指定值的类型
const typedSet = new Set<number>();
typedSet.add(1); // 正确
typedSet.add('1'); // 错误,值应该是number类型
// 复杂类型示例
interface Product {
id: number;
name: string;
}
const productSet = new Set<Product>();
productSet.add({ id: 1, name: 'Laptop' });
// 从现有类型派生Set类型
type ProductSet = Set<Product>;
5.Set 与数组的区别
特性 | Set | 数组 |
---|---|---|
唯一性 | 值唯一 | 允许重复值 |
查找效率 | has方法O(1) | includes/indexOf O(n) |
添加元素 | add方法 | push/unshift等方法 |
删除元素 | delete方法 | splice/filter等方法 |
顺序 | 插入顺序 | 可排序 |
索引访问 | 不支持 | 支持下标访问 |
大小获取 | size属性 | length属性 |
内存占用 | 通常比数组多 | 通常更节省内存 |
6.使用场景
- 需要存储唯一值的场景
- 需要快速检查某个值是否存在的场景
- 需要去重的场景
- 数学集合运算(并集、交集、差集)
7.集合运算示例
// 并集
const union = new Set([...setA, ...setB]);
// 交集
const intersection = new Set([...setA].filter(x => setB.has(x)));
// 差集 (A - B)
const difference = new Set([...setA].filter(x => !setB.has(x)));
三、Map 和 Set 的性能特点
- 查找速度:Map 和 Set 的查找操作(has/get)平均时间复杂度为 O(1),比数组的 O(n) 快
- 插入速度:Map 和 Set 的插入操作平均时间复杂度为 O(1)
- 删除速度:Map 和 Set 的删除操作平均时间复杂度为 O(1)
- 内存占用:Map 和 Set 通常比对象和数组占用更多内存
- 迭代性能:Map和Set的迭代速度与数组相当,对象迭代需要Object.keys等额外步骤
四、应用建议
- 类型安全:
// 总是明确指定泛型类型
const userMap = new Map<number, User>();
- 合理选择数据结构:
-
需要键值对 → Map
-
需要唯一值 → Set
-
需要索引访问 → 数组
-
需要简单键值且不频繁修改 → 对象
- 内存管理:
-
对于临时对象作为键/值,考虑WeakMap/WeakSet
-
大型Map/Set不再需要时手动clear()
- 性能优化:
-
避免在热代码路径中频繁创建新的Map/Set
-
对于大型数据集,考虑预分配大小
- 不可变数据:
// 需要不可变Map/Set时
function addToImmutableSet<T>(set: Set<T>, value: T): Set<T> {
return new Set([...set, value]);
}
- 与数组转换:
// Map转数组
const mapEntries = [...map.entries()];
// Set转数组
const setValues = [...set];
- 自定义相等性:
// 对于对象值,需要自定义相等逻辑
const objectSet = new Set<{id: number}>();
const obj1 = {id: 1};
const obj2 = {id: 1};
objectSet.add(obj1);
objectSet.has(obj2); // false,因为对象引用不同
五、总结
特性 | Map | Set |
---|---|---|
存储内容 | 键值对 | 唯一值 |
键/值类型 | 任意类型 | 任意类型 |
主要方法 | set, get, has, delete | add, has, delete |
遍历方式 | keys, values, entries | values, keys, entries |
典型用途 | 需要非字符串键的键值对存储 | 值唯一性检查、集合运算 |
是否有序 | 插入顺序 | 插入顺序 |
1.何时选择Map/Set:
-
需要非字符串键 → Map
-
需要严格维护插入顺序 → Map/Set
-
需要频繁增删元素 → Map/Set
-
需要高效存在性检查 → Set
-
需要集合运算 → Set
2.何时选择对象/数组:
-
需要索引访问 → 数组
-
简单配置对象 → 对象
-
需要方法操作(如map/filter)→ 数组
-
小型静态数据集 → 对象/数组
在 TypeScript 中使用 Map 和 Set 时,建议总是明确指定泛型类型参数,以获得更好的类型检查和代码提示。这两种数据结构在处理特定问题时比传统的对象和数组更高效、更直观。