随着 JavaScript 应用变得越来越复杂,性能优化也变得至关重要。接下来我们一起深入探讨一下性能优化的技巧之----WeakMap
什么是WeakMap
WeakMap 是 JavaScript 的一个内置对象,用于存储键值对,其键是对象,值可以是任意类型。与普通的 Map 不同,WeakMap 的键是弱引用,这意味着如果没有其他引用指向键对象,这些对象会自动被垃圾回收机制回收。(垃圾回收机制:浏览器的自动内存管理机制,会自动回收不需要用的内存,使其重新可用。以此来保证内存的充足)
注意:WeakMap
的键必须是对象
语法
new WeakMap([iterable])
iterable
:是一个数组(二元数组)或者其他可迭代的且其元素是键值对的对象。每个键值都会被添加到新的 WeakMap 中。如果 iterable 为 null,会被当作 undefined。
属性
length
:属性的值为 0;prototype
:WeakMap 构造函数的原型对象,允许向所有 WeakMap 实例添加属性。
WeakMap
方法
set(key, value)
: 设置键值对,返回这个 WeakMap 对象。。get(key)
: 获取键对应的值,如果key
不存在返回undefined。has(key)
: 检查是否存在某个键,返回一个布尔值。delete(key)
: 删除某个键值对。
方法使用示例
创建
创建 WeakMap
const weakMap = new WeakMap();
当然我们也可以在创建时传入一个包含键值对的数组来初始化 WeakMap
:
const key1 = {};
const key2 = {};
const weakMap = new WeakMap([
[key1, 'value1'],
[key2, 'value2']
]);
添加
向 WeakMap 中添加或更新键值对,它会返回 WeakMap
本身,以便可以进行链式调用。
const obj = {};
const returnValue = weakMap.set(obj, 'some value');
console.log(returnValue); // 输出: WeakMap { <items unknown> }
获取
get(key)
返回与指定键关联的值,如果该键不存在,则返回 undefined
。
console.log(weakMap.get(obj)); // 输出 'some value'
console.log(weakMap.get(a)); // 输出: undefined
判断是否存在
has(key)
返回一个布尔值,表示 WeakMap 中是否存在指定的键。
console.log(weakMap.has(obj)); // 输出 true
删除
delete(key)
移除 WeakMap
中指定的键值对,它返回一个布尔值,表示是否成功删除。
console.log(weakMap.delete(obj)); // 输出: true
console.log(weakMap.has(obj)); // 输出 false
console.log(weakMap.delete(obj)); // 输出: false
看到这里可能会有很多读者有有一种似曾相识的感觉,没错它是不是和我们所熟知的Map
很相像。那它很Map
有什么区别呢
Map 和 WeakMap 的区别
特性 | Map | WeakMap |
---|---|---|
键的类型 | 可以是任何类型 | 只能是对象引用 |
垃圾回收 | 不会自动清除无引用的键值对 | 无引用的键值对会被自动清除 |
可枚举性 | 可以枚举键、值和条目 | 不可枚举,无法获取集合的大小 |
内部实现 | 使用数组存储键值对 | 使用弱引用存储键值对 |
时间复杂度 | 赋值和搜索操作为 O(n) | 赋值和搜索操作平均时间复杂度为 O(1) |
内存管理 | 可能导致内存泄漏 | 不会导致内存泄漏 |
方法 | set() , get() , has() , delete() , clear() , keys() , values() , entries() | set() , get() , has() , delete() |
用途 | 通用键值对存储 | 与对象关联的元数据或私有数据存储 |
性能优化举例:
在需要频繁计算耗时操作的场景中,可以利用 WeakMap 缓存计算结果,以避免重复计算,这样我们在使用的时候就可以立马调用。提高性能并且WeakMap
不需要我们担心内存泄漏的问题。
const cache = new WeakMap(); // 创建一个 WeakMap 用于缓存对象的计算结果
function computeExpensiveOperation(obj) {
if (!cache.has(obj)) { // 如果缓存中没有存储当前对象的计算结果
const result = obj.value * 2; // 假设这是一个耗时的计算,将 obj 的 value 属性乘以 2
cache.set(obj, result); // 将计算结果存入 WeakMap 中,以 obj 作为键
}
return cache.get(obj); // 返回 WeakMap 中存储的 obj 对应的计算结果
}
let myObj1 = { value: 10 }; // 创建一个对象 myObj1,value 属性为 10
let myObj2 = { value: 20 }; // 创建一个对象 myObj2,value 属性为 20
console.log(computeExpensiveOperation(myObj1)); // 第一次调用,计算 myObj1 的计算结果并缓存
console.log(computeExpensiveOperation(myObj1)); // 直接从缓存中获取 myObj1 的计算结果,无需重新计算
console.log(computeExpensiveOperation(myObj2)); // 计算 myObj2 的计算结果并缓存,因为是不同的对象
存储对象的私有数据或元数据
在处理复杂的需求时,我们经常需要为 DOM 元素关联一些自定义的数据或元数据。使用 WeakMap 可以有效地存储和管理这些数据,同时确保不会影响垃圾回收对 DOM 元素的处理。
const elementData = new WeakMap(); // 创建一个 WeakMap 用于存储 DOM 元素和其用户数据的映射关系
function setUserData(element, userData) {
elementData.set(element, userData); // 将 userData 存储到 WeakMap 中,以 element 作为键
}
function getUserData(element) {
return elementData.get(element); // 获取 WeakMap 中存储的 element 对应的 userData
}
const divElement = document.createElement('div'); // 创建一个新的 div 元素
setUserData(divElement, { name: 'John', age: 30 }); // 设置 div 元素的用户数据为 { name: 'John', age: 30 }
console.log(getUserData(divElement)); // 输出 { name: 'John', age: 30 },获取并打印 div 元素的用户数据
防止内存泄漏的事件监听器管理
在管理事件监听器时,需要特别注意避免因事件监听器未被正确移除而导致的内存泄漏问题。使用 WeakMap
可以有效地管理事件监听器,确保在元素或程序结束时自动移除监听器,而不需要手动清理。
const eventListeners = new WeakMap(); // 创建一个 WeakMap 用于存储事件监听器
function addEventListener(element, event, listener) {
if (!eventListeners.has(element)) { // 如果 WeakMap 中没有存储当前元素的事件监听器
eventListeners.set(element, new Map()); // 在 WeakMap 中为当前元素创建一个新的 Map,用于存储事件和监听器
}
const elementEvents = eventListeners.get(element); // 获取当前元素对应的事件和监听器 Map
if (!elementEvents.has(event)) { // 如果当前事件在 Map 中不存在
elementEvents.set(event, []); // 在事件 Map 中为当前事件创建一个空数组,用于存储监听器
}
elementEvents.get(event).push(listener); // 将监听器添加到事件对应的监听器数组中
element.addEventListener(event, listener); // 向元素添加事件监听器
}
function removeEventListener(element, event, listener) {
if (eventListeners.has(element)) { // 如果 WeakMap 中存在当前元素的事件监听器
const elementEvents = eventListeners.get(element); // 获取当前元素对应的事件和监听器 Map
if (elementEvents.has(event)) { // 如果当前事件在事件 Map 中存在
const listeners = elementEvents.get(event); // 获取当前事件对应的监听器数组
const index = listeners.indexOf(listener); // 查找监听器在数组中的索引
if (index !== -1) { // 如果找到了监听器
listeners.splice(index, 1); // 从监听器数组中移除该监听器
element.removeEventListener(event, listener); // 从元素中移除事件监听器
if (listeners.length === 0) { // 如果当前事件的监听器数组为空
elementEvents.delete(event); // 从事件 Map 中删除当前事件
}
}
}
}
}
const button = document.createElement('button'); // 创建一个新的按钮元素
addEventListener(button, 'click', () => { // 给按钮元素添加点击事件监听器
console.log('Button clicked!'); // 当按钮被点击时输出信息到控制台
});
// 程序结束或元素移除时,无需手动移除事件监听器,垃圾回收会自动处理
扩展
其实除了WeakMap
外,它还有两个很相似的兄弟WeakRef
和WeakSet
,我们这里简单对比一下他们三个之间的差别
当然,可以更详细地对比 WeakRef
、WeakMap
和 WeakSet
。以下是一个更全面的对比表格:
特性 | WeakRef | WeakMap | WeakSet |
---|---|---|---|
存储类型 | 单个对象 | 键-值对,键必须是对象 | 对象集合 |
键类型 | 不适用 | 只能是对象 | 只能是对象 |
值类型 | 对象 | 任意类型 | 不适用 |
键/值是否被强引用 | 否(弱引用) | 键强引用,值在键被回收后弱引用 | 否(弱引用) |
用途中对象的处理 | 提供真正的弱引用,允许对象在生命周期的任意时刻被回收 | 存储对象键-值对,在键被回收后也可以回收相应的值 | 存储对象集合,在对象不再被引用时可以被回收 |
垃圾回收特性 | 在对象不再被引用时自动回收 | 当键对象被回收后,值对象也会被回收 | 在对象不再被引用时自动回收 |
主要用途 | 高级内存管理,避免内存泄漏 | 缓存、对象映射等需要对象键值对的场景 | 存储唯一对象的集合,例如跟踪已处理的对象 |
API 方法 | deref() | set() , get() , delete() , has() | add() , delete() , has() |
键是否唯一 | 不适用 | 是 | 是 |
值是否唯一 | 是 | 否 | 是 |
可枚举性 | 否 | 否 | 否 |
内存管理优势 | 减少内存泄漏 | 在键被垃圾回收后,自动回收其关联的值 | 在对象不再被引用时,自动释放内存 |
适用版本 | ECMAScript 2021 (ES12) | ECMAScript 2015 (ES6) | ECMAScript 2015 (ES6) |
兼容性 | 较新,支持情况相对较少 | 已广泛支持 | 已广泛支持 |
典型使用场景 | 缓存管理、保持对象引用但允许垃圾回收 | 缓存、DOM节点映射、存储私有数据 | 跟踪唯一对象集合、管理对象生命周期 |
代码示例 | javascript\nlet weakRef = new WeakRef(obj);\nlet derefObj = weakRef.deref();\n | javascript\nlet weakMap = new WeakMap();\nweakMap.set(obj, val);\nlet value = weakMap.get(obj);\n | javascript\nlet weakSet = new WeakSet();\nweakSet.add(obj);\nlet hasObj = weakSet.has(obj);\n |
内存回收时机 | 在对象没有其他引用时可被回收 | 在键没有其他引用时,其关联的值可被回收 | 在对象没有其他引用时可被回收 |
弱引用的特性 | 提供真正的弱引用,在对象的生命周期中不影响垃圾回收 | 仅在键被回收后才弱引用值 | 在对象不再被引用时,自动允许回收 |