new Map应用
浏览量:加载中...
技巧开发

JavaScript Map 实例教程 - 由浅入深
1. Map 基础概念
Map 是 JavaScript 中的一种集合类型,它保存键值对,并记住键的原始插入顺序
与普通对象不同,Map 可以使用任何类型的值作为键(包括对象、函数等)
1// 创建一个空的 Map 2const emptyMap = new Map(); 3console.log('空 Map:', emptyMap); // Map(0) {} 4 5// 创建带有初始值的 Map 6const initialMap = new Map([ 7 ['key1', 'value1'], 8 ['key2', 'value2'], 9 [123, '数字键'], 10]); 11console.log('带初始值的 Map:', initialMap); 12// Map(3) { 'key1' => 'value1', 'key2' => 'value2', 123 => '数字键' }
2. Map 基本操作
1// 添加和获取元素 2const map = new Map(); 3map.set('name', '张三'); 4map.set('age', 25); 5console.log('设置后的 Map:', map); 6// Map(2) { 'name' => '张三', 'age' => 25 } 7 8// 获取值 get 9console.log('获取 name:', map.get('name')); 10// 张三 11console.log('获取不存在的键:', map.get('nonexistent')); 12// undefined 13 14// 检查键是否存在 has 15console.log('检查 name 是否存在:', map.has('name')); // true 16console.log('检查不存在的键:', map.has('nonexistent')); // false 17 18// 删除元素 delete 19map.delete('age'); 20console.log('删除 age 后的 Map:', map); 21// Map(1) { 'name' => '张三' } 22 23// 清空 Map 24map.clear(); 25console.log('清空后的 Map:', map); 26// Map(0) {}
3. Map 与 Object 的区别
1// 1. 键的类型 2const objKey = { id: 1 }; 3const arrKey = [1, 2, 3]; 4 5const mapWithObjKey = new Map(); 6mapWithObjKey.set(objKey, '对象作为键'); 7mapWithObjKey.set(arrKey, '数组作为键'); 8 9console.log('Map 使用对象作为键:', mapWithObjKey.get(objKey)); 10// 对象作为键 11console.log('Map 使用数组作为键:', mapWithObjKey.get(arrKey)); 12// 数组作为键 13 14// 尝试在普通对象中使用对象作为键(会被转换为字符串) 15const obj = {}; 16obj[objKey] = '对象作为键'; 17console.log('Object 使用对象作为键:', obj); 18// { '[object Object]': '对象作为键' } 19 20// 2. 大小获取 21console.log('Map 大小:', mapWithObjKey.size); 22// 2 23console.log('Object 键的数量:', Object.keys(obj).length); 24// 1 25 26// 3. 迭代顺序 27const orderedMap = new Map(); 28orderedMap.set(3, 'c'); 29orderedMap.set(1, 'a'); 30orderedMap.set(2, 'b'); 31console.log('Map 保持插入顺序:', Array.from(orderedMap.entries())); 32// [ [ 3, 'c' ], [ 1, 'a' ], [ 2, 'b' ] ] 33 34const orderedObj = {}; 35orderedObj[3] = 'c'; 36orderedObj[1] = 'a'; 37orderedObj[2] = 'b'; 38console.log('Object 键的顺序:', Object.keys(orderedObj).map(Number)); 39// [ '1', '2', '3' ] => [1, 2, 3]
4. Map 迭代方法
1const fruitMap = new Map([ 2 ['apple', 5], 3 ['banana', 3], 4 ['orange', 8], 5]); 6 7// 1. 使用 keys() 迭代键 8console.log('迭代键:'); 9for (const key of fruitMap.keys()) { 10 console.log(key); 11 // apple, banana, orange (每个单独一行) 12} 13 14// 2. 使用 values() 迭代值 15console.log('\n 迭代值:'); 16for (const value of fruitMap.values()) { 17 console.log(value); 18 // 5, 3, 8 (每个单独一行) 19} 20 21// 3. 使用 entries() 迭代键值对 一般是对象方法 转为数组然后迭代 隐性 22console.log('\n 迭代键值对:'); 23for (const [key, value] of fruitMap.entries()) { 24 console.log(`${key}: ${value}`); 25 // apple: 5, banana: 3, orange: 8 (每个单独一行) 26} 27 28// 4. 使用 forEach() 29console.log('\n 使用 forEach:'); 30fruitMap.forEach((value, key) => { 31 console.log(`${key}: ${value}`); // apple: 5, banana: 3, orange: 8 (每个单独一行) 32}); 33 34// 5. 直接迭代 Map(默认迭代 entries) 35console.log('\n 直接迭代 Map:'); 36for (const [key, value] of fruitMap) { 37 console.log(`${key}: ${value}`); 38 // apple: 5, banana: 3, orange: 8 (每个单独一行) 39}
5. Map 转换
1const mapToArray = Array.from(fruitMap); 2console.log('Map 转数组:', mapToArray); 3// [ [ 'apple', 5 ], [ 'banana', 3 ], [ 'orange', 8 ] ] 4 5// Map 转对象 🚩 6const mapToObject = Object.fromEntries(fruitMap); 7console.log('Map 转对象:', mapToObject); 8// { apple: 5, banana: 3, orange: 8 } 9 10// 对象转 Map 🚩 11const objToMap = new Map(Object.entries(mapToObject)); 12console.log('对象转 Map:', objToMap); 13// Map(3) { 'apple' => 5, 'banana' => 3, 'orange' => 8 } 14 15// 数组转 Map 🚩 16const arrayToMap = new Map([ 17 ['x', 1], 18 ['y', 2], 19]); 20console.log('数组转 Map:', arrayToMap); 21// Map(2) { 'x' => 1, 'y' => 2 }
6. Map 实用场景
1 2// 1. 缓存计算结果 3const cache = new Map(); 4 5function fibonacci(n) { 6if (n <= 1) return n; 7 8// 检查缓存 9if (cache.has(n)) { 10console.log(`从缓存获取 fib(${n})`); 11return cache.get(n); 12} 13 14// 计算并缓存结果 15const result = fibonacci(n - 1) + fibonacci(n - 2); 16cache.set(n, result); 17console.log(`计算并缓存 fib(${n})`); 18return result; 19} 20 21console.log('fibonacci(5):', fibonacci(5)); 22// 5 (输出多行计算过程) 23console.log('fibonacci(5) (从缓存):', fibonacci(5)); 24// 5 (输出一行从缓存获取) 25console.log('缓存内容:', Array.from(cache.entries())); 26// [ [ 2, 1 ], [ 3, 2 ], [ 4, 3 ], [ 5, 5 ] ] 27 28// 2. 记录对象关联数据 29const userData = new Map(); 30 31const user1 = { id: 1, name: '张三' }; 32const user2 = { id: 2, name: '李四' }; 33 34// 为每个用户关联额外数据 35userData.set(user1, { loginCount: 5, lastLogin: new Date() }); 36userData.set(user2, { loginCount: 12, lastLogin: new Date('2023-05-10') }); 37 38console.log('用户 1 数据:', userData.get(user1)); 39// { loginCount: 5, lastLogin: [当前日期] } 40console.log('用户 2 数据:', userData.get(user2)); 41// { loginCount: 12, lastLogin: 2023-05-10T00:00:00.000Z } 42 43// 3. 实现私有数据 44class Counter { 45constructor() { 46// 使用 Map 存储私有数据,外部无法直接访问 47this.\_privateData = new Map(); 48this.\_privateData.set('count', 0); 49} 50 51increment() { 52const count = this.\_privateData.get('count'); 53this.\_privateData.set('count', count + 1); 54return this.\_privateData.get('count'); 55} 56 57getCount() { 58return this.\_privateData.get('count'); 59} 60} 61 62const counter = new Counter(); 63console.log('初始计数:', counter.getCount()); // 0 64console.log('增加后:', counter.increment()); // 1 65console.log('再次增加:', counter.increment()); // 2 66console.log('最终计数:', counter.getCount()); // 2
7. WeakMap 简介
WeakMap 与 Map 的区别:
-
WeakMap 只接受对象作为键
-
WeakMap 的键是弱引用,当键对象没有其他引用时,会被垃圾回收
-
WeakMap 不可迭代,没有 size 属性
1const weakMap = new WeakMap(); 2let obj1 = { name: '对象 1' }; 3const obj2 = { name: '对象 2' }; 4 5weakMap.set(obj1, '这是对象 1 的关联数据'); 6weakMap.set(obj2, '这是对象 2 的关联数据'); 7 8console.log('WeakMap 获取 obj1 的值:', weakMap.get(obj1)); 9// 这是对象 1 的关联数据 10 11// 当 obj1 被设置为 null,没有其他引用时,垃圾回收器会回收 obj1 12// 同时 WeakMap 中对应的条目也会被自动清除 13obj1 = null; 14console.log('obj1 设置为 null 后,WeakMap 中的条目会被垃圾回收'); 15// 无输出
8. Map 性能考虑
Map 在频繁增删键值对的场景下性能优于 Object
Map 在查找性能上与 Object 相当,但在大量数据时可能略有优势
1// 性能测试示例 2const largeMap = new Map(); 3const largeObj = {}; 4 5// 填充大量数据 6const dataSize = 10000; 7console.time('Map 填充时间'); 8for (let i = 0; i < dataSize; i++) { 9 largeMap.set(`key${i}`, `value${i}`); 10} 11console.timeEnd('Map 填充时间'); 12// 输出类似: Map 填充时间: 5.123ms 13 14console.time('Object 填充时间'); 15for (let i = 0; i < dataSize; i++) { 16 largeObj[`key${i}`] = `value${i}`; 17} 18console.timeEnd('Object 填充时间'); 19// 输出类似: Object 填充时间: 3.456ms 20 21// 查找测试 22const randomKey = `key${Math.floor(Math.random() * dataSize)}`; 23 24console.time('Map 查找时间'); 25largeMap.get(randomKey); 26console.timeEnd('Map 查找时间'); 27// 输出类似: Map 查找时间: 0.012ms 28 29console.time('Object 查找时间'); 30largeObj[randomKey]; 31console.timeEnd('Object 查找时间'); 32// 输出类似: Object 查找时间: 0.008ms
9. Map 高级实用案例
9.1 LRU 缓存实现 (最近最少使用)
1class LRUCache { 2 constructor(capacity) { 3 this.capacity = capacity; 4 this.cache = new Map(); // 使用 Map 保持插入顺序 5 } 6 7 get(key) { 8 if (!this.cache.has(key)) { 9 return -1; // 未找到 10 } 11 12 // 获取值,然后删除并重新插入,使其成为最新使用的 13 const value = this.cache.get(key); 14 this.cache.delete(key); 15 this.cache.set(key, value); 16 return value; 17 } 18 19 put(key, value) { 20 // 如果键已存在,先删除旧的 21 if (this.cache.has(key)) { 22 this.cache.delete(key); 23 } 24 // 如果缓存已满,删除最久未使用的项(Map 的第一个元素) 25 else if (this.cache.size >= this.capacity) { 26 const firstKey = this.cache.keys().next().value; 27 this.cache.delete(firstKey); 28 console.log(`缓存已满,删除最久未使用的键: ${firstKey}`); 29 } 30 31 // 添加新项 32 this.cache.set(key, value); 33 } 34 35 // 获取当前缓存内容 36 getCache() { 37 return Array.from(this.cache.entries()); 38 } 39} 40 41// 创建容量为 3 的 LRU 缓存 42const lruCache = new LRUCache(3); 43 44// 添加缓存项 45lruCache.put('a', 1); 46lruCache.put('b', 2); 47lruCache.put('c', 3); 48console.log('初始缓存:', lruCache.getCache()); 49// [ [ 'a', 1 ], [ 'b', 2 ], [ 'c', 3 ] ] 50 51// 访问项 'a',使其成为最新使用的 52console.log('获取 a 的值:', lruCache.get('a')); // 1 53console.log('访问 a 后的缓存:', lruCache.getCache()); 54// [ [ 'b', 2 ], [ 'c', 3 ], [ 'a', 1 ] ] 55 56// 添加新项 'd',会删除最久未使用的 'b' 57lruCache.put('d', 4); 58console.log('添加 d 后的缓存:', lruCache.getCache()); 59// [ [ 'c', 3 ], [ 'a', 1 ], [ 'd', 4 ] ]
9.2 按钮节流控制
1class ButtonThrottle { 2 constructor() { 3 this.buttonStates = new Map(); // 存储每个按钮的状态 4 } 5 6 // 按钮节流方法 7 throttle(buttonId, callback, delay = 1000) { 8 const now = Date.now(); 9 const lastClickTime = this.buttonStates.get(buttonId) || 0; 10 11 if (now - lastClickTime >= delay) { 12 this.buttonStates.set(buttonId, now); 13 callback(); 14 return true; // 执行了回调 15 } else { 16 console.log(`按钮 ${buttonId} 节流中,请稍后再试`); 17 return false; // 被节流 18 } 19 } 20 21 // 清除指定按钮的节流状态 22 clearButton(buttonId) { 23 this.buttonStates.delete(buttonId); 24 console.log(`已清除按钮 ${buttonId} 的节流状态`); 25 } 26 27 // 清除所有按钮的节流状态 28 clearAllButtons() { 29 this.buttonStates.clear(); 30 console.log('已清除所有按钮的节流状态'); 31 } 32} 33 34// 创建按钮节流控制器 35const buttonThrottle = new ButtonThrottle(); 36 37// 模拟按钮点击 38function simulateButtonClick(buttonId) { 39 const success = buttonThrottle.throttle( 40 buttonId, 41 () => { 42 console.log(`按钮 ${buttonId} 操作执行成功!`); 43 }, 44 2000 45 ); // 2 秒节流 46 47 if (!success) { 48 console.log(`按钮 ${buttonId} 操作被节流阻止`); 49 } 50} 51 52// 测试多个按钮的节流 53console.log('测试按钮 A 的连续点击:'); 54simulateButtonClick('buttonA'); // 执行成功 55simulateButtonClick('buttonA'); // 被节流 56simulateButtonClick('buttonA'); // 被节流 57 58console.log('\n 测试按钮 B 的点击:'); 59simulateButtonClick('buttonB'); // 执行成功 60 61console.log('\n 等待 2 秒后再次点击按钮 A:'); 62setTimeout(() => { 63 simulateButtonClick('buttonA'); // 执行成功 64}, 2100);
9.3 接口请求缓存
1class ApiCache { 2 constructor(maxSize = 10, ttl = 60000) { 3 // 默认最大缓存 10 个接口,TTL 60 秒 4 this.cache = new Map(); 5 this.maxSize = maxSize; 6 this.ttl = ttl; // 生存时间(毫秒) 7 } 8 9 // 生成缓存键 10 generateKey(url, params = {}) { 11 const paramString = Object.keys(params) 12 .sort() 13 .map((key) => `${key}=${params[key]}`) 14 .join('&'); 15 return `${url}${paramString ? '?' + paramString : ''}`; 16 } 17 18 // 获取缓存数据 19 get(url, params = {}) { 20 const key = this.generateKey(url, params); 21 const item = this.cache.get(key); 22 23 if (!item) { 24 return null; // 缓存未命中 25 } 26 27 // 检查是否过期 28 if (Date.now() - item.timestamp > this.ttl) { 29 this.cache.delete(key); 30 console.log(`缓存已过期,删除键: ${key}`); 31 return null; 32 } 33 34 console.log(`从缓存获取数据: ${key}`); 35 return item.data; 36 } 37 38 // 设置缓存数据 39 set(url, params = {}, data) { 40 const key = this.generateKey(url, params); 41 42 // 如果缓存已满,删除最旧的项 43 if (this.cache.size >= this.maxSize && !this.cache.has(key)) { 44 const firstKey = this.cache.keys().next().value; 45 this.cache.delete(firstKey); 46 console.log(`缓存已满,删除最旧的接口缓存: ${firstKey}`); 47 } 48 49 this.cache.set(key, { 50 data, 51 timestamp: Date.now(), 52 }); 53 54 console.log(`缓存接口数据: ${key}`); 55 } 56 57 // 清除指定接口的缓存 58 clear(url, params = {}) { 59 const key = this.generateKey(url, params); 60 if (this.cache.delete(key)) { 61 console.log(`已清除接口缓存: ${key}`); 62 return true; 63 } 64 return false; 65 } 66 67 // 清除所有缓存 68 clearAll() { 69 const size = this.cache.size; 70 this.cache.clear(); 71 console.log(`已清除所有接口缓存,共 ${size} 项`); 72 } 73 74 // 获取缓存统计信息 75 getStats() { 76 return { 77 size: this.cache.size, 78 maxSize: this.maxSize, 79 ttl: this.ttl, 80 keys: Array.from(this.cache.keys()), 81 }; 82 } 83} 84 85// 创建接口缓存管理器 86const apiCache = new ApiCache(5, 5000); // 最多缓存 5 个接口,TTL 5 秒 87 88// 模拟 API 请求函数 89async function fetchApiData(url, params = {}) { 90 // 先尝试从缓存获取 91 const cachedData = apiCache.get(url, params); 92 if (cachedData) { 93 return cachedData; 94 } 95 96 // 模拟 API 请求 97 console.log(`发起API请求: ${url}`); 98 // 这里应该是实际的 API 请求,我们用 setTimeout 模拟 99 await new Promise((resolve) => setTimeout(resolve, 500)); 100 101 // 模拟返回数据 102 const data = { 103 url, 104 params, 105 data: `模拟数据-${Date.now()}`, 106 timestamp: new Date().toISOString(), 107 }; 108 109 // 存入缓存 110 apiCache.set(url, params, data); 111 112 return data; 113} 114 115// 测试接口缓存 116async function testApiCache() { 117 console.log('第一次请求用户数据:'); 118 let userData = await fetchApiData('/api/user', { id: 1 }); 119 console.log('用户数据:', userData); 120 121 console.log('\n 第二次请求相同用户数据(应从缓存获取):'); 122 userData = await fetchApiData('/api/user', { id: 1 }); 123 console.log('用户数据:', userData); 124 125 console.log('\n 请求不同参数的用户数据:'); 126 userData = await fetchApiData('/api/user', { id: 2 }); 127 console.log('用户数据:', userData); 128 129 console.log('\n 请求产品数据:'); 130 let productData = await fetchApiData('/api/product', { 131 category: 'electronics', 132 }); 133 console.log('产品数据:', productData); 134 135 console.log('\n 请求订单数据:'); 136 let orderData = await fetchApiData('/api/order', { status: 'pending' }); 137 console.log('订单数据:', orderData); 138 139 console.log('\n 请求新闻数据(这会触发缓存淘汰):'); 140 let newsData = await fetchApiData('/api/news', { page: 1 }); 141 console.log('新闻数据:', newsData); 142 143 console.log('\n 当前缓存统计:'); 144 console.log(apiCache.getStats()); 145 146 console.log('\n 等待 6 秒后再次请求(缓存应已过期):'); 147 setTimeout(async () => { 148 userData = await fetchApiData('/api/user', { id: 1 }); 149 console.log('用户数据:', userData); 150 }, 6100); 151} 152 153// 执行测试 154testApiCache();
10. Map 更多经典用法与开发技巧
10.1 使用 Map 实现发布订阅模式
1class EventEmitter { 2 constructor() { 3 // 使用 Map 存储事件和对应的回调函数列表 4 this.events = new Map(); 5 } 6 7 // 订阅事件 8 on(eventName, callback) { 9 if (!this.events.has(eventName)) { 10 this.events.set(eventName, []); 11 } 12 this.events.get(eventName).push(callback); 13 console.log(`已订阅事件: ${eventName}`); 14 return this; // 支持链式调用 15 } 16 17 // 取消订阅 18 off(eventName, callback) { 19 if (!this.events.has(eventName)) return this; 20 21 const callbacks = this.events.get(eventName); 22 const index = callbacks.indexOf(callback); 23 if (index > -1) { 24 callbacks.splice(index, 1); 25 console.log(`已取消订阅事件: ${eventName}`); 26 } 27 28 // 如果没有回调函数了,删除事件 29 if (callbacks.length === 0) { 30 this.events.delete(eventName); 31 } 32 33 return this; 34 } 35 36 // 触发事件 37 emit(eventName, ...args) { 38 if (!this.events.has(eventName)) return this; 39 40 console.log(`触发事件: ${eventName}, 参数:`, args); 41 this.events.get(eventName).forEach((callback) => { 42 callback(...args); 43 }); 44 45 return this; 46 } 47 48 // 只订阅一次 49 once(eventName, callback) { 50 const onceCallback = (...args) => { 51 callback(...args); 52 this.off(eventName, onceCallback); 53 }; 54 this.on(eventName, onceCallback); 55 return this; 56 } 57 58 // 获取所有事件名 59 getEventNames() { 60 return Array.from(this.events.keys()); 61 } 62 63 // 获取指定事件的监听器数量 64 listenerCount(eventName) { 65 return this.events.has(eventName) ? this.events.get(eventName).length : 0; 66 } 67} 68 69// 测试发布订阅模式 70const emitter = new EventEmitter(); 71 72const userClickHandler = (data) => console.log('用户点击处理:', data); 73const analyticsHandler = (data) => console.log('数据分析处理:', data); 74 75// 订阅事件 76emitter.on('click', userClickHandler).on('click', analyticsHandler); 77 78// 触发事件 79emitter.emit('click', { button: 'submit', timestamp: Date.now() }); 80 81// 取消一个订阅 82emitter.off('click', userClickHandler); 83emitter.emit('click', { button: 'cancel', timestamp: Date.now() });
10.2 使用 Map 实现依赖注入容器
1class DIContainer { 2 constructor() { 3 // 存储服务实例 4 this.instances = new Map(); 5 // 存储服务工厂函数 6 this.factories = new Map(); 7 // 存储单例标记 8 this.singletons = new Map(); 9 } 10 11 // 注册服务 12 register(name, factory, singleton = true) { 13 this.factories.set(name, factory); 14 this.singletons.set(name, singleton); 15 console.log(`注册服务: ${name}, 单例: ${singleton}`); 16 return this; 17 } 18 19 // 解析服务 20 resolve(name) { 21 // 检查是否已有实例(单例模式) 22 if (this.instances.has(name)) { 23 console.log(`从容器获取单例服务: ${name}`); 24 return this.instances.get(name); 25 } 26 27 // 检查是否注册了工厂函数 28 if (!this.factories.has(name)) { 29 throw new Error(`服务 ${name} 未注册`); 30 } 31 32 // 创建新实例 33 const factory = this.factories.get(name); 34 const instance = factory(this); 35 36 // 如果是单例,缓存实例 37 if (this.singletons.get(name)) { 38 this.instances.set(name, instance); 39 console.log(`创建并缓存单例服务: ${name}`); 40 } else { 41 console.log(`创建非单例服务: ${name}`); 42 } 43 44 return instance; 45 } 46 47 // 清除所有实例 48 clearInstances() { 49 this.instances.clear(); 50 console.log('已清除所有服务实例'); 51 } 52 53 // 检查服务是否已注册 54 isRegistered(name) { 55 return this.factories.has(name); 56 } 57} 58 59// 测试依赖注入 60const container = new DIContainer(); 61 62// 注册一些服务 63container.register('database', () => { 64 return { 65 query: (sql) => `执行SQL: ${sql}`, 66 connect: () => console.log('数据库连接已建立'), 67 }; 68}); 69 70container.register('userService', (container) => { 71 const db = container.resolve('database'); 72 return { 73 getUser: (id) => db.query(`SELECT * FROM users WHERE id = ${id}`), 74 createUser: (user) => 75 db.query(`INSERT INTO users SET ${JSON.stringify(user)}`), 76 }; 77}); 78 79container.register( 80 'logger', 81 () => { 82 return { 83 log: (message) => console.log(`[日志] ${message}`), 84 error: (message) => console.error(`[错误] ${message}`), 85 }; 86 }, 87 false 88); // 非单例 89 90// 解析并使用服务 91const userService = container.resolve('userService'); 92console.log(userService.getUser(1)); 93 94const logger1 = container.resolve('logger'); 95const logger2 = container.resolve('logger'); 96console.log(`两个logger实例是否相同: ${logger1 === logger2}`); // false,因为是非单例 97 98const userService2 = container.resolve('userService'); 99console.log(`两个userService实例是否相同: ${userService === userService2}`); // true,因为是单例
10.3 使用 Map 实现状态管理器
1class StateManager { 2 constructor(initialState = {}) { 3 // 存储状态 4 this.state = new Map(Object.entries(initialState)); 5 // 存储订阅者 6 this.subscribers = new Map(); 7 // 中间件 8 this.middlewares = []; 9 } 10 11 // 获取状态 12 getState(key) { 13 if (key === undefined) { 14 // 返回所有状态的对象形式 15 return Object.fromEntries(this.state); 16 } 17 return this.state.get(key); 18 } 19 20 // 设置状态 21 setState(updates) { 22 const prevState = Object.fromEntries(this.state); 23 24 // 应用更新 25 if (typeof updates === 'function') { 26 updates = updates(prevState); 27 } 28 29 Object.entries(updates).forEach(([key, value]) => { 30 this.state.set(key, value); 31 }); 32 33 const nextState = Object.fromEntries(this.state); 34 35 // 通知订阅者 36 this.notify(prevState, nextState); 37 38 return nextState; 39 } 40 41 // 订阅状态变化 42 subscribe(key, callback) { 43 if (!this.subscribers.has(key)) { 44 this.subscribers.set(key, []); 45 } 46 47 this.subscribers.get(key).push(callback); 48 49 // 返回取消订阅函数 50 return () => { 51 const callbacks = this.subscribers.get(key); 52 const index = callbacks.indexOf(callback); 53 if (index > -1) { 54 callbacks.splice(index, 1); 55 } 56 }; 57 } 58 59 // 通知订阅者 60 notify(prevState, nextState) { 61 // 找出所有变化的状态键 62 const changedKeys = Object.keys(nextState).filter( 63 (key) => prevState[key] !== nextState[key] 64 ); 65 66 // 通知特定键的订阅者 67 changedKeys.forEach((key) => { 68 if (this.subscribers.has(key)) { 69 this.subscribers.get(key).forEach((callback) => { 70 callback(nextState[key], prevState[key]); 71 }); 72 } 73 }); 74 75 // 通知全局订阅者(使用 '*' 作为键) 76 if (this.subscribers.has('*')) { 77 this.subscribers.get('*').forEach((callback) => { 78 callback(nextState, prevState); 79 }); 80 } 81 } 82 83 // 添加中间件 84 use(middleware) { 85 this.middlewares.push(middleware); 86 return this; 87 } 88 89 // 应用中间件 90 applyMiddleware(updates) { 91 return this.middlewares.reduce((acc, middleware) => { 92 return middleware(acc); 93 }, updates); 94 } 95} 96 97// 测试状态管理 98const store = new StateManager({ 99 user: { name: '张三', age: 25 }, 100 count: 0, 101 loading: false, 102}); 103 104// 添加中间件:日志记录 105store.use((updates) => { 106 console.log('状态更新:', updates); 107 return updates; 108}); 109 110// 订阅用户状态变化 111const unsubscribeUser = store.subscribe('user', (newUser, oldUser) => { 112 console.log('用户状态变化:', { from: oldUser, to: newUser }); 113}); 114 115// 订阅计数状态变化 116store.subscribe('count', (newCount, oldCount) => { 117 console.log('计数变化:', { from: oldCount, to: newCount }); 118}); 119 120// 全局订阅 121store.subscribe('*', (newState, oldState) => { 122 console.log('全局状态变化:', { from: oldState, to: newState }); 123}); 124 125// 更新状态 126store.setState({ 127 user: { ...store.getState('user'), age: 26 }, 128 count: store.getState('count') + 1, 129}); 130 131// 使用函数形式更新 132store.setState((prevState) => ({ 133 count: prevState.count + 1, 134 loading: true, 135}));
10.4 使用 Map 实现路由管理
1class Router { 2 constructor() { 3 // 存储路由映射 4 this.routes = new Map(); 5 // 存储中间件 6 this.middlewares = []; 7 // 存储参数 8 this.params = {}; 9 } 10 11 // 添加路由 12 add(path, handler) { 13 this.routes.set(path, handler); 14 console.log(`添加路由: ${path}`); 15 return this; 16 } 17 18 // 添加 GET 路由 19 get(path, handler) { 20 return this.add(`GET:${path}`, handler); 21 } 22 23 // 添加 POST 路由 24 post(path, handler) { 25 return this.add(`POST:${path}`, handler); 26 } 27 28 // 添加中间件 29 use(middleware) { 30 this.middlewares.push(middleware); 31 return this; 32 } 33 34 // 路由匹配 35 match(method, path) { 36 const routeKey = `${method}:${path}`; 37 38 // 精确匹配 39 if (this.routes.has(routeKey)) { 40 return { 41 handler: this.routes.get(routeKey), 42 params: {}, 43 }; 44 } 45 46 // 参数匹配(简化版) 47 for (const [route, handler] of this.routes) { 48 const [routeMethod, routePath] = route.split(':'); 49 if (routeMethod !== method) continue; 50 51 // 检查路径参数 52 const routeSegments = routePath.split('/'); 53 const pathSegments = path.split('/'); 54 55 if (routeSegments.length !== pathSegments.length) continue; 56 57 const params = {}; 58 let match = true; 59 60 for (let i = 0; i < routeSegments.length; i++) { 61 const routeSegment = routeSegments[i]; 62 const pathSegment = pathSegments[i]; 63 64 // 如果路由段以:开头,则是参数 65 if (routeSegment.startsWith(':')) { 66 const paramName = routeSegment.substring(1); 67 params[paramName] = pathSegment; 68 } else if (routeSegment !== pathSegment) { 69 match = false; 70 break; 71 } 72 } 73 74 if (match) { 75 return { handler, params }; 76 } 77 } 78 79 return null; 80 } 81 82 // 执行路由 83 async handle(method, path) { 84 const match = this.match(method, path); 85 86 if (!match) { 87 console.log(`未找到路由: ${method} ${path}`); 88 return { status: 404, message: 'Not Found' }; 89 } 90 91 const { handler, params } = match; 92 this.params = params; 93 94 console.log(`匹配到路由: ${method} ${path}, 参数:`, params); 95 96 // 创建上下文 97 const context = { 98 method, 99 path, 100 params, 101 query: this.parseQuery(path), 102 body: null, 103 }; 104 105 // 执行中间件 106 for (const middleware of this.middlewares) { 107 await middleware(context); 108 } 109 110 // 执行处理器 111 return await handler(context); 112 } 113 114 // 解析查询参数 115 parseQuery(path) { 116 const queryIndex = path.indexOf('?'); 117 if (queryIndex === -1) return {}; 118 119 const queryString = path.substring(queryIndex + 1); 120 const params = new URLSearchParams(queryString); 121 const result = {}; 122 123 for (const [key, value] of params) { 124 result[key] = value; 125 } 126 127 return result; 128 } 129 130 // 获取所有路由 131 getRoutes() { 132 return Array.from(this.routes.keys()); 133 } 134} 135 136// 测试路由管理 137const router = new Router(); 138 139// 添加中间件 140router.use((context) => { 141 console.log(`中间件处理: ${context.method} ${context.path}`); 142 // 可以在这里添加认证、日志等功能 143}); 144 145// 添加路由 146router.get('/', () => ({ message: '首页' })); 147router.get('/users', () => ({ users: ['张三', '李四'] })); 148router.get('/users/:id', (context) => ({ 149 user: `用户ID: ${context.params.id}`, 150})); 151router.post('/users', (context) => ({ 152 message: '创建用户', 153 data: context.body, 154})); 155 156// 测试路由 157router.handle('GET', '/').then((result) => console.log('路由结果:', result)); 158router 159 .handle('GET', '/users') 160 .then((result) => console.log('路由结果:', result)); 161router 162 .handle('GET', '/users/123') 163 .then((result) => console.log('路由结果:', result)); 164router 165 .handle('POST', '/users') 166 .then((result) => console.log('路由结果:', result)); 167router 168 .handle('GET', '/posts') 169 .then((result) => console.log('路由结果:', result));
10.5 使用 Map 实现简单的缓存装饰器
1function memoize(fn, keyGenerator = (...args) => JSON.stringify(args)) { 2 const cache = new Map(); 3 4 const memoizedFn = function (...args) { 5 // 创建缓存键 6 const key = keyGenerator(...args); 7 8 // 检查缓存 9 if (cache.has(key)) { 10 console.log(`从缓存获取 ${fn.name}(${args.join(', ')}) 的结果`); 11 return cache.get(key); 12 } 13 14 // 执行原方法 15 const result = fn.apply(this, args); 16 17 // 存入缓存 18 cache.set(key, result); 19 console.log(`缓存 ${fn.name}(${args.join(', ')}) 的结果`); 20 21 return result; 22 }; 23 24 // 添加清除缓存的方法 25 memoizedFn.clearCache = function () { 26 cache.clear(); 27 console.log(`已清除 ${fn.name} 的缓存`); 28 }; 29 30 return memoizedFn; 31} 32 33// 测试缓存装饰器 34class Calculator { 35 constructor() { 36 const self = this; 37 38 // 使用memoize函数包装方法 39 this.fibonacci = memoize(function (n) { 40 if (n <= 1) return n; 41 return self.fibonacci(n - 1) + self.fibonacci(n - 2); 42 }); 43 44 this.multiply = memoize(function (a, b) { 45 console.log(`执行乘法计算: ${a} * ${b}`); 46 return a * b; 47 }); 48 } 49} 50 51const calculator = new Calculator(); 52 53// 第一次调用会执行计算并缓存 54console.log('fibonacci(10):', calculator.fibonacci(10)); 55 56// 第二次调用会从缓存获取 57console.log('fibonacci(10):', calculator.fibonacci(10)); 58 59// 第一次调用乘法 60console.log('multiply(5, 6):', calculator.multiply(5, 6)); 61 62// 第二次调用乘法会从缓存获取 63console.log('multiply(5, 6):', calculator.multiply(5, 6)); 64 65// 清除缓存 66calculator.multiply.clearCache(); 67 68// 再次调用会重新计算 69console.log('multiply(5, 6):', calculator.multiply(5, 6));
10.6 使用 Map 实现简单的依赖关系管理
1class DependencyGraph { 2 constructor() { 3 // 存储节点及其依赖 4 this.dependencies = new Map(); 5 // 存储反向依赖(哪些节点依赖于当前节点) 6 this.dependents = new Map(); 7 } 8 9 // 添加依赖关系 10 addDependency(node, dependency) { 11 // 如果节点不存在,初始化 12 if (!this.dependencies.has(node)) { 13 this.dependencies.set(node, new Set()); 14 } 15 16 // 如果依赖不存在,初始化 17 if (!this.dependents.has(dependency)) { 18 this.dependents.set(dependency, new Set()); 19 } 20 21 // 添加依赖关系 22 this.dependencies.get(node).add(dependency); 23 this.dependents.get(dependency).add(node); 24 25 console.log(`添加依赖: ${node} 依赖于 ${dependency}`); 26 } 27 28 // 移除依赖关系 29 removeDependency(node, dependency) { 30 if (this.dependencies.has(node)) { 31 this.dependencies.get(node).delete(dependency); 32 } 33 34 if (this.dependents.has(dependency)) { 35 this.dependents.get(dependency).delete(node); 36 } 37 38 console.log(`移除依赖: ${node} 不再依赖于 ${dependency}`); 39 } 40 41 // 获取节点的所有依赖 42 getDependencies(node) { 43 return Array.from(this.dependencies.get(node) || []); 44 } 45 46 // 获取依赖于当前节点的所有节点 47 getDependents(node) { 48 return Array.from(this.dependents.get(node) || []); 49 } 50 51 // 检查是否存在循环依赖 52 hasCircularDependency() { 53 const visited = new Set(); 54 const recursionStack = new Set(); 55 56 const hasCycle = (node) => { 57 if (recursionStack.has(node)) { 58 return true; // 发现循环 59 } 60 61 if (visited.has(node)) { 62 return false; // 已处理过 63 } 64 65 visited.add(node); 66 recursionStack.add(node); 67 68 // 检查所有依赖 69 const deps = this.dependencies.get(node); 70 if (deps) { 71 for (const dep of deps) { 72 if (hasCycle(dep)) { 73 return true; 74 } 75 } 76 } 77 78 recursionStack.delete(node); 79 return false; 80 }; 81 82 // 检查所有节点 83 for (const node of this.dependencies.keys()) { 84 if (hasCycle(node)) { 85 return true; 86 } 87 } 88 89 return false; 90 } 91 92 // 拓扑排序(获取执行顺序) 93 topologicalSort() { 94 if (this.hasCircularDependency()) { 95 throw new Error('存在循环依赖,无法进行拓扑排序'); 96 } 97 98 const visited = new Set(); 99 const result = []; 100 101 const visit = (node) => { 102 if (visited.has(node)) return; 103 104 visited.add(node); 105 106 // 先访问所有依赖 107 const deps = this.dependencies.get(node); 108 if (deps) { 109 for (const dep of deps) { 110 visit(dep); 111 } 112 } 113 114 result.push(node); 115 }; 116 117 // 访问所有节点 118 for (const node of this.dependencies.keys()) { 119 visit(node); 120 } 121 122 return result; 123 } 124} 125 126// 测试依赖关系管理 127const dependencyGraph = new DependencyGraph(); 128 129// 添加依赖关系 130dependencyGraph.addDependency('component', 'utils'); 131dependencyGraph.addDependency('component', 'api'); 132dependencyGraph.addDependency('page', 'component'); 133dependencyGraph.addDependency('app', 'page'); 134dependencyGraph.addDependency('app', 'router'); 135 136// 获取依赖 137console.log('component 的依赖:', dependencyGraph.getDependencies('component')); 138console.log('utils 的依赖者:', dependencyGraph.getDependents('utils')); 139 140// 检查循环依赖 141console.log('是否存在循环依赖:', dependencyGraph.hasCircularDependency()); 142 143// 获取执行顺序 144console.log('拓扑排序结果:', dependencyGraph.topologicalSort()); 145 146// 添加循环依赖 147dependencyGraph.addDependency('utils', 'component'); 148console.log( 149 '添加循环依赖后,是否存在循环依赖:', 150 dependencyGraph.hasCircularDependency() 151);
10.7 使用 Map 实现简单的 LRU 函数缓存
1function createLRUCacheFunction(fn, maxSize = 10) { 2const cache = new Map(); 3 4return function (...args) { 5const key = JSON.stringify(args); 6 7 // 如果缓存命中,删除并重新插入(更新为最近使用) 8 if (cache.has(key)) { 9 const value = cache.get(key); 10 cache.delete(key); 11 cache.set(key, value); 12 console.log(`LRU缓存命中: ${fn.name}(${args.join(', ')})`); 13 return value; 14 } 15 16 // 执行函数 17 const result = fn.apply(this, args); 18 19 // 如果缓存已满,删除最久未使用的项 20 if (cache.size >= maxSize) { 21 const firstKey = cache.keys().next().value; 22 cache.delete(firstKey); 23 console.log(`LRU缓存已满,删除最久未使用的缓存: ${firstKey}`); 24 } 25 26 // 添加到缓存 27 cache.set(key, result); 28 console.log(`LRU缓存添加: ${fn.name}(${args.join(', ')})`); 29 30 return result; 31 32}; 33} 34 35// 测试 LRU 函数缓存 36function slowOperation(n) { 37console.log(`执行慢操作: 计算平方 ${n}²`); 38return n \* n; 39} 40 41const cachedSlowOperation = createLRUCacheFunction(slowOperation, 3); 42 43// 执行几次操作 44console.log(cachedSlowOperation(5)); // 执行并缓存 45console.log(cachedSlowOperation(10)); // 执行并缓存 46console.log(cachedSlowOperation(15)); // 执行并缓存 47console.log(cachedSlowOperation(5)); // 从缓存获取 48console.log(cachedSlowOperation(20)); // 执行并缓存,会删除最久未使用的(10) 49console.log(cachedSlowOperation(10)); // 重新执行,因为已被删除
Map 高级用法总结
- Map 非常适合实现发布订阅模式,可以高效管理事件和回调;
- Map 可以用来实现依赖注入容器,管理服务的创建和生命周期;
- Map 可以用于状态管理,高效存储和更新应用状态;
- Map 可以用于路由管理,支持参数匹配和中间件;
- Map 可以用于实现缓存装饰器,提高函数执行效率;
- Map 可以用于管理依赖关系,支持循环检测和拓扑排序;
- Map 可以用于实现 LRU 缓存,自动管理缓存大小和淘汰策略;
- Map 在需要保持插入顺序、频繁增删键值对的场景中性能优异;