【秋招备战】JavaScript 集合类型深度解析
在 JavaScript 的世界里,除了我们熟悉的对象(Object)和数组(Array),ES6 还引入了四种新的集合类型:Map、Set、WeakMap 和 WeakSet。这些数据结构不仅丰富了我们的工具箱,更是在现代前端框架的底层实现中扮演着重要角色。今天,让我们深入探讨这些集合类型的特性、使用场景,以及它们在 React 和 Vue 源码中的精妙应用。
一、Map:更强大的键值对集合
基本概念
Map 是一个键值对的集合,与普通对象最大的区别在于:Map 的键可以是任意类型,而不仅仅是字符串或 Symbol。
const map = new Map();
map.set('name', 'JavaScript'); map.set(42, 'The Answer'); map.set(true, 'Boolean key');
const objKey = { id: 1 }; map.set(objKey, 'Object as key');
const funcKey = () => {}; map.set(funcKey, 'Function as key');
console.log(map.get(objKey)); console.log(map.size);
|
Map 的核心特性
- 键的唯一性:Map 中的键是唯一的,重复设置会覆盖之前的值
- 保持插入顺序:Map 会记住键值对的插入顺序
- 可迭代:Map 实现了迭代器协议
const map = new Map([ ['first', 1], ['second', 2], ['third', 3] ]);
for (const [key, value] of map) { console.log(`${key}: ${value}`); }
console.log([...map.keys()]);
console.log([...map.values()]);
console.log([...map.entries()]);
|
Map vs Object 的性能对比
const testSize = 100000;
console.time('Object'); const obj = {}; for (let i = 0; i < testSize; i++) { obj[`key${i}`] = i; delete obj[`key${i}`]; } console.timeEnd('Object');
console.time('Map'); const map = new Map(); for (let i = 0; i < testSize; i++) { map.set(`key${i}`, i); map.delete(`key${i}`); } console.timeEnd('Map');
|
实际应用场景
class LRUCache { constructor(capacity) { this.capacity = capacity; this.cache = new Map(); }
get(key) { if (!this.cache.has(key)) return -1; const value = this.cache.get(key); this.cache.delete(key); this.cache.set(key, value); return value; }
put(key, value) { if (this.cache.has(key)) { this.cache.delete(key); } this.cache.set(key, value); if (this.cache.size > this.capacity) { const firstKey = this.cache.keys().next().value; this.cache.delete(firstKey); } } }
|
二、Set:独特值的集合
基本概念
Set 是一个值的集合,其中每个值都是唯一的,不会重复。
const set = new Set();
set.add(1); set.add(2); set.add(2); set.add('2');
console.log(set.size); console.log(set.has(2));
const numbers = [1, 2, 2, 3, 4, 4, 5]; const uniqueNumbers = [...new Set(numbers)]; console.log(uniqueNumbers);
|
Set 的高级应用
const setA = new Set([1, 2, 3, 4]); const setB = new Set([3, 4, 5, 6]);
const union = new Set([...setA, ...setB]); console.log([...union]);
const intersection = new Set([...setA].filter(x => setB.has(x))); console.log([...intersection]);
const difference = new Set([...setA].filter(x => !setB.has(x))); console.log([...difference]);
const users = [ { id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }, { id: 1, name: 'Alice' }, { id: 3, name: 'Charlie' } ];
const uniqueUsers = Array.from( new Map(users.map(user => [user.id, user])).values() ); console.log(uniqueUsers);
|
性能优势
const arr = Array.from({ length: 10000 }, (_, i) => i); const set = new Set(arr);
console.time('Array includes'); for (let i = 0; i < 1000; i++) { arr.includes(9999); } console.timeEnd('Array includes');
console.time('Set has'); for (let i = 0; i < 1000; i++) { set.has(9999); } console.timeEnd('Set has');
|
三、WeakMap:弱引用的键值对集合
核心特性
WeakMap 与 Map 类似,但有几个关键区别:
- 键必须是对象(不能是原始值)
- 键是弱引用的,不会阻止垃圾回收
- 不可迭代,没有 size 属性
const wm = new WeakMap();
const obj1 = { name: 'object1' }; const obj2 = { name: 'object2' };
wm.set(obj1, 'value1'); wm.set(obj2, 'value2');
console.log(wm.get(obj1));
let obj3 = { name: 'object3' }; wm.set(obj3, 'value3'); obj3 = null;
|
WeakMap 的实际应用
const privateData = new WeakMap();
class Person { constructor(name, age) { privateData.set(this, { name, age }); }
getName() { return privateData.get(this).name; }
getAge() { return privateData.get(this).age; } }
const person = new Person('Alice', 30); console.log(person.getName()); console.log(person.name);
const elementData = new WeakMap();
function attachData(element, data) { elementData.set(element, data); }
function getData(element) { return elementData.get(element); }
const button = document.querySelector('#myButton'); attachData(button, { clickCount: 0 });
button.addEventListener('click', () => { const data = getData(button); data.clickCount++; console.log(`Clicked ${data.clickCount} times`); });
|
内存管理优势
const cache = new Map();
function processData(obj) { if (cache.has(obj)) { return cache.get(obj); } const result = expensiveOperation(obj); cache.set(obj, result); return result; }
const weakCache = new WeakMap();
function processDataSafe(obj) { if (weakCache.has(obj)) { return weakCache.get(obj); } const result = expensiveOperation(obj); weakCache.set(obj, result); return result; }
|
四、WeakSet:弱引用的值集合
基本特性
WeakSet 与 Set 类似,但具有以下特点:
- 只能存储对象
- 对象是弱引用的
- 不可迭代
const ws = new WeakSet();
const obj1 = { id: 1 }; const obj2 = { id: 2 };
ws.add(obj1); ws.add(obj2);
console.log(ws.has(obj1));
ws.add(obj1);
|
实际应用场景
const disabledElements = new WeakSet();
function disableElement(element) { disabledElements.add(element); element.classList.add('disabled'); }
function enableElement(element) { disabledElements.delete(element); element.classList.remove('disabled'); }
function isDisabled(element) { return disabledElements.has(element); }
const processing = new WeakSet();
function processObject(obj) { if (processing.has(obj)) { console.log('Already processing this object'); return; } processing.add(obj); try { console.log('Processing:', obj); if (obj.children) { obj.children.forEach(child => processObject(child)); } } finally { processing.delete(obj); } }
|
五、在 React 中的应用
React Fiber 中的 WeakMap 使用
React 的 Fiber 架构中使用 WeakMap 来存储组件相关的信息:
const fiberNodeMap = new WeakMap();
function createFiberNode(element) { const fiber = { type: element.type, props: element.props, }; if (element.dom) { fiberNodeMap.set(element.dom, fiber); } return fiber; }
function getFiberFromDOM(domElement) { return fiberNodeMap.get(domElement); }
|
React Hooks 中的 Map 应用
let currentComponent = null; const hooksMap = new Map();
function useState(initialState) { const component = currentComponent; if (!hooksMap.has(component)) { hooksMap.set(component, []); } const hooks = hooksMap.get(component); const hookIndex = hooks.length; if (hooks[hookIndex] === undefined) { hooks[hookIndex] = { state: initialState, setState: (newState) => { hooks[hookIndex].state = newState; reRender(component); } }; } return [hooks[hookIndex].state, hooks[hookIndex].setState]; }
|
六、在 Vue 中的应用
Vue 3 响应式系统中的 WeakMap
Vue 3 的响应式系统大量使用了 WeakMap 来存储依赖关系:
const targetMap = new WeakMap();
function track(target, key) { let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = new Set())); } dep.add(activeEffect); }
function trigger(target, key) { const depsMap = targetMap.get(target); if (!depsMap) return; const dep = depsMap.get(key); if (!dep) return; dep.forEach(effect => effect()); }
function reactive(target) { return new Proxy(target, { get(target, key, receiver) { track(target, key); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { const result = Reflect.set(target, key, value, receiver); trigger(target, key); return result; } }); }
|
Vue 组件实例管理
const instanceMap = new WeakMap();
class VueComponent { constructor(options) { this.options = options; if (options.el) { instanceMap.set(options.el, this); } } static getInstance(element) { return instanceMap.get(element); } }
const cache = new Map(); const keys = new Set();
function pruneCache(keepAliveInstance, filter) { cache.forEach((entry, key) => { const name = getComponentName(entry.componentInstance); if (name && !filter(name)) { pruneCacheEntry(key); } }); }
function pruneCacheEntry(key) { const cached = cache.get(key); if (cached) { cached.componentInstance.$destroy(); } cache.delete(key); keys.delete(key); }
|
七、性能优化最佳实践
选择合适的数据结构
const permissions = ['read', 'write', 'delete']; if (permissions.includes('write')) { }
const permissionSet = new Set(['read', 'write', 'delete']); if (permissionSet.has('write')) { }
const elementDataMap = new Map();
const elementDataWeakMap = new WeakMap();
const orderedMap = new Map([ ['first', 1], ['second', 2], ['third', 3] ]);
|
内存管理注意事项
class EventManager { constructor() { this.listeners = new WeakMap(); } addEventListener(element, event, handler) { if (!this.listeners.has(element)) { this.listeners.set(element, new Map()); } const elementListeners = this.listeners.get(element); if (!elementListeners.has(event)) { elementListeners.set(event, new Set()); } elementListeners.get(event).add(handler); element.addEventListener(event, handler); } removeEventListener(element, event, handler) { const elementListeners = this.listeners.get(element); if (!elementListeners) return; const eventHandlers = elementListeners.get(event); if (!eventHandlers) return; eventHandlers.delete(handler); element.removeEventListener(event, handler); if (eventHandlers.size === 0) { elementListeners.delete(event); } if (elementListeners.size === 0) { this.listeners.delete(element); } } }
|
八、总结与建议
何时使用 Map/Set
- Map:当你需要键值对集合,且键可能是对象或需要保持插入顺序时
- Set:当你需要存储唯一值,或进行集合运算(并集、交集、差集)时
何时使用 WeakMap/WeakSet
- WeakMap:当你需要将数据与对象关联,且不想阻止对象被垃圾回收时
- WeakSet:当你需要标记对象,或创建对象的弱引用集合时
性能考虑
- 查找性能:Map/Set 的查找是 O(1),而数组是 O(n)
- 内存管理:WeakMap/WeakSet 有助于防止内存泄漏
- 迭代性能:Map/Set 可以直接迭代,性能优于对象的 Object.keys()
这四种集合类型极大地丰富了 JavaScript 的数据结构选择,合理使用它们不仅能让代码更简洁、更具表现力,还能在性能和内存管理方面带来显著的提升。在现代前端框架的开发中,它们更是不可或缺的基础设施。