Map 是 ES6 新增的键值对集合,与对象不同的是,Map 的键可以是任意类型。
基本用法#
创建 Map#
// 空 Mapconst map1 = new Map()
// 从二维数组创建const map2 = new Map([ ['name', '张三'], ['age', 25],])console.log(map2) // Map {'name' => '张三', 'age' => 25}
// 从其他 Map 创建const map3 = new Map(map2)
// 从 Object 创建const obj = { a: 1, b: 2 }const map4 = new Map(Object.entries(obj))设置和获取#
const map = new Map()
// 设置map.set('name', '张三')map.set(123, 'number key')map.set(true, 'boolean key')map.set({ id: 1 }, 'object key')map.set(null, 'null key')map.set(undefined, 'undefined key')
// 链式调用map.set('a', 1).set('b', 2).set('c', 3)
// 获取map.get('name') // '张三'map.get(123) // 'number key'map.get('notexist') // undefined对象作为键#
const map = new Map()
const key1 = { id: 1 }const key2 = { id: 1 }
map.set(key1, 'value1')map.set(key2, 'value2')
// 不同对象是不同的键console.log(map.size) // 2console.log(map.get(key1)) // 'value1'console.log(map.get(key2)) // 'value2'console.log(map.get({ id: 1 })) // undefined(新对象,不同引用)判断和删除#
const map = new Map([['name', '张三']])
// 判断存在map.has('name') // truemap.has('age') // false
// 删除map.delete('name') // truemap.delete('notexist') // false
// 清空map.clear()console.log(map.size) // 0遍历方法#
Map 的遍历顺序就是插入顺序:
const map = new Map([ ['name', '张三'], ['age', 25], ['city', '北京'],])
// keys()for (const key of map.keys()) { console.log(key) // 'name', 'age', 'city'}
// values()for (const value of map.values()) { console.log(value) // '张三', 25, '北京'}
// entries()for (const [key, value] of map.entries()) { console.log(key, value)}
// forEachmap.forEach((value, key, map) => { console.log(key, value)})
// for...of(默认使用 entries())for (const [key, value] of map) { console.log(key, value)}转换#
const map = new Map([ ['a', 1], ['b', 2],])
// Map 转数组;[...map] // [['a', 1], ['b', 2]];[...map.keys()] // ['a', 'b'];[...map.values()] // [1, 2]
// Map 转对象Object.fromEntries(map) // { a: 1, b: 2 }
// 对象转 Mapconst obj = { x: 1, y: 2 }new Map(Object.entries(obj)) // Map {'x' => 1, 'y' => 2}Map vs Object#
| 特性 | Map | Object |
|---|---|---|
| 键类型 | 任意类型 | 字符串或 Symbol |
| 顺序 | 保持插入顺序 | 基本保持(但有特殊规则) |
| 大小 | map.size | Object.keys(obj).length |
| 遍历 | 直接可迭代 | 需要 Object.keys/entries |
| 性能 | 频繁增删更优 | 少量数据更优 |
| 原型链 | 无原型污染 | 可能继承属性 |
// Object 的键只能是字符串const obj = {}obj[1] = 'one'obj['1'] = 'string one'console.log(obj[1]) // 'string one'(被覆盖)
// Map 区分类型const map = new Map()map.set(1, 'one')map.set('1', 'string one')console.log(map.get(1)) // 'one'console.log(map.get('1')) // 'string one'实际应用#
缓存#
const cache = new Map()
function memoize(fn) { return function (...args) { const key = JSON.stringify(args) if (cache.has(key)) { return cache.get(key) } const result = fn.apply(this, args) cache.set(key, result) return result }}
const expensiveFn = memoize((n) => { console.log('Computing...') return n * n})
expensiveFn(5) // 'Computing...' 25expensiveFn(5) // 25(从缓存读取)计数器#
function countOccurrences(arr) { const counts = new Map() for (const item of arr) { counts.set(item, (counts.get(item) || 0) + 1) } return counts}
const words = ['apple', 'banana', 'apple', 'cherry', 'banana', 'apple']const counts = countOccurrences(words)// Map {'apple' => 3, 'banana' => 2, 'cherry' => 1}DOM 元素关联数据#
const elementData = new Map()
function setData(element, key, value) { if (!elementData.has(element)) { elementData.set(element, new Map()) } elementData.get(element).set(key, value)}
function getData(element, key) { return elementData.get(element)?.get(key)}
const button = document.querySelector('button')setData(button, 'clicks', 0)setData(button, 'lastClick', null)双向映射#
class BiMap { #forward = new Map() #reverse = new Map()
set(key, value) { this.#forward.set(key, value) this.#reverse.set(value, key) }
get(key) { return this.#forward.get(key) }
getKey(value) { return this.#reverse.get(value) }
delete(key) { const value = this.#forward.get(key) this.#forward.delete(key) this.#reverse.delete(value) }}
const userIds = new BiMap()userIds.set('user_001', '张三')userIds.get('user_001') // '张三'userIds.getKey('张三') // 'user_001'WeakMap#
WeakMap 与 Map 类似,但键必须是对象,且是弱引用。
特点#
- 键必须是对象
- 键是弱引用(不阻止垃圾回收)
- 不可遍历(没有 size、keys、values 等)
const wm = new WeakMap()
const key = { id: 1 }wm.set(key, 'value')
console.log(wm.get(key)) // 'value'console.log(wm.has(key)) // true
wm.delete(key)console.log(wm.has(key)) // false
// 不能使用原始值作为键// wm.set('string', 'value'); // TypeError// wm.set(1, 'value'); // TypeErrorWeakMap 应用场景#
私有属性#
const privateData = new WeakMap()
class Person { constructor(name, age) { privateData.set(this, { name, age }) }
get name() { return privateData.get(this).name }
get age() { return privateData.get(this).age }
birthday() { privateData.get(this).age++ }}
const person = new Person('张三', 25)console.log(person.name) // '张三'console.log(person.age) // 25person.birthday()console.log(person.age) // 26
// 外部无法访问 privateDataDOM 节点关联数据#
const nodeData = new WeakMap()
function processNode(node) { if (nodeData.has(node)) { return nodeData.get(node) }
const data = { processed: true, timestamp: Date.now(), } nodeData.set(node, data) return data}
// 当 DOM 节点被移除时,关联数据自动被垃圾回收缓存计算结果#
const resultsCache = new WeakMap()
function heavyComputation(obj) { if (resultsCache.has(obj)) { console.log('From cache') return resultsCache.get(obj) }
console.log('Computing...') const result = /* 复杂计算 */ Object.keys(obj).length resultsCache.set(obj, result) return result}
const data = { a: 1, b: 2, c: 3 }heavyComputation(data) // 'Computing...' 3heavyComputation(data) // 'From cache' 3
// 当 data 不再被引用时,缓存自动清除事件监听器管理#
const listeners = new WeakMap()
function addListener(element, event, handler) { if (!listeners.has(element)) { listeners.set(element, new Map()) } const elementListeners = listeners.get(element)
if (!elementListeners.has(event)) { elementListeners.set(event, new Set()) } elementListeners.get(event).add(handler)
element.addEventListener(event, handler)}
function removeAllListeners(element) { const elementListeners = listeners.get(element) if (!elementListeners) return
for (const [event, handlers] of elementListeners) { for (const handler of handlers) { element.removeEventListener(event, handler) } } listeners.delete(element)}Map vs WeakMap 对比#
| 特性 | Map | WeakMap |
|---|---|---|
| 键类型 | 任意 | 仅对象 |
| 引用类型 | 强引用 | 弱引用 |
| 可遍历 | 是 | 否 |
| size 属性 | 有 | 无 |
| 垃圾回收 | 手动管理 | 自动清理 |
小结#
| 方法/属性 | 说明 |
|---|---|
| new Map() | 创建空 Map |
| set(key, value) | 设置键值 |
| get(key) | 获取值 |
| has(key) | 检查键是否存在 |
| delete(key) | 删除键值对 |
| clear() | 清空 |
| size | 键值对数量 |
| keys()/values()/entries() | 遍历器 |
| forEach() | 遍历 |
Map 适合需要任意类型键的场景,WeakMap 适合需要避免内存泄漏的对象关联数据存储。