JavaScript集合Map与WeakMap使用最佳实践
作者:时间sk
一、Map集合类型基础
1.1 什么是Map?
Map是ES6引入的一种新的集合类型,用于存储键值对(key-value)集合。与传统对象(Object)类似,但具有更强大的功能和灵活性。
Map vs Object的主要区别:
| 特性 | Map | Object |
|---|---|---|
| 键类型 | 可以是任意类型(字符串、数字、对象、函数等) | 主要是字符串或Symbol |
| 键顺序 | 保留插入顺序 | ES6之前不保证顺序,ES6之后为插入顺序 |
| 大小获取 | 直接通过size属性 | 需要手动计算(Object.keys(obj).length) |
| 迭代方式 | 可直接迭代 | 需要通过Object.keys()等方法间接迭代 |
| 默认键 | 无 | 原型链上的属性可能冲突 |
1.2 创建Map和基本操作
// 1. 创建Map
// 方式1: 使用构造函数创建空Map
const map1 = new Map();
// 方式2: 通过数组初始化Map
const map2 = new Map([
['name', '张三'],
['age', 30],
['isStudent', false]
]);
console.log('map2初始化结果:', map2);
// 输出: Map(3) { 'name' => '张三', 'age' => 30, 'isStudent' => false }
// 2. 添加元素 - set(key, value)
// 返回Map对象本身,因此可以链式调用
map1.set('id', 1)
.set('name', '李四')
.set('age', 25);
console.log('map1添加元素后:', map1);
// 输出: Map(3) { 'id' => 1, 'name' => '李四', 'age' => 25 }
// 3. 获取元素 - get(key)
const name = map2.get('name');
const age = map2.get('age');
const address = map2.get('address'); // 不存在的键返回undefined
console.log('获取元素 - name:', name); // 输出: 获取元素 - name: 张三
console.log('获取元素 - age:', age); // 输出: 获取元素 - age: 30
console.log('获取不存在的键:', address); // 输出: 获取不存在的键: undefined
// 4. 判断键是否存在 - has(key)
const hasName = map2.has('name');
const hasAddress = map2.has('address');
console.log('是否有name键:', hasName); // 输出: 是否有name键: true
console.log('是否有address键:', hasAddress); // 输出: 是否有address键: false
// 5. 删除元素 - delete(key)
const deleteAge = map2.delete('age'); // 删除成功返回true
const deleteAddress = map2.delete('address'); // 删除不存在的键返回false
console.log('删除age键是否成功:', deleteAge); // 输出: 删除age键是否成功: true
console.log('删除后map2:', map2); // 输出: 删除后map2: Map(2) { 'name' => '张三', 'isStudent' => false }
console.log('删除不存在的键:', deleteAddress); // 输出: 删除不存在的键: false
// 6. 清空Map - clear()
map1.clear();
console.log('map1清空后:', map1); // 输出: map1清空后: Map(0) {}
// 7. 获取Map大小 - size属性
console.log('map2当前大小:', map2.size); // 输出: map2当前大小: 2运行结果:
map2初始化结果: Map(3) { 'name' => '张三', 'age' => 30, 'isStudent' => false }
map1添加元素后: Map(3) { 'id' => 1, 'name' => '李四', 'age' => 25 }
获取元素 - name: 张三
获取元素 - age: 30
获取不存在的键: undefined
是否有name键: true
是否有address键: false
删除age键是否成功: true
删除后map2: Map(2) { 'name' => '张三', 'isStudent' => false }
删除不存在的键: false
map1清空后: Map(0) {}
map2当前大小: 2
1.3 Map的键可以是任意类型
与对象不同,Map的键可以是任意类型,包括对象、函数、NaN等。
// 创建各种类型的键
const objKey = { id: 1 };
const funcKey = () => console.log('hello');
const numKey = 123;
const boolKey = true;
const symbolKey = Symbol('symbol');
// 创建Map并添加不同类型的键值对
const map = new Map();
map.set(objKey, '对象作为键')
.set(funcKey, '函数作为键')
.set(numKey, '数字作为键')
.set(boolKey, '布尔值作为键')
.set(symbolKey, 'Symbol作为键')
.set(NaN, 'NaN作为键');
// 获取值
console.log('对象键对应的值:', map.get(objKey)); // 输出: 对象键对应的值: 对象作为键
console.log('函数键对应的值:', map.get(funcKey)); // 输出: 函数键对应的值: 函数作为键
console.log('NaN键对应的值:', map.get(NaN)); // 输出: NaN键对应的值: NaN作为键
// 注意: NaN虽然不等于自身,但在Map中被视为同一个键
console.log('NaN === NaN:', NaN === NaN); // 输出: NaN === NaN: false
map.set(NaN, '更新NaN键的值');
console.log('更新后NaN键对应的值:', map.get(NaN)); // 输出: 更新后NaN键对应的值: 更新NaN键的值
// 注意: 两个看起来相同的对象是不同的键
const objKey2 = { id: 1 }; // 与objKey看起来相同但不是同一个对象
map.set(objKey2, '另一个对象作为键');
console.log('objKey对应的值:', map.get(objKey)); // 输出: objKey对应的值: 对象作为键
console.log('objKey2对应的值:', map.get(objKey2)); // 输出: objKey2对应的值: 另一个对象作为键
console.log('map大小:', map.size); // 输出: map大小: 7运行结果:
对象键对应的值: 对象作为键
函数键对应的值: 函数作为键
NaN键对应的值: NaN作为键
NaN === NaN: false
更新后NaN键对应的值: 更新NaN键的值
objKey对应的值: 对象作为键
objKey2对应的值: 另一个对象作为键
map大小: 7
1.4 Map的迭代方法
Map提供了多种迭代方式,可以方便地遍历键、值或键值对。
// 创建一个Map
const fruits = new Map([
['apple', '苹果'],
['banana', '香蕉'],
['orange', '橙子'],
['grape', '葡萄']
]);
// 1. 使用forEach迭代
console.log('1. 使用forEach迭代:');
fruits.forEach((value, key) => {
console.log(`${key}: ${value}`);
});
// 2. 迭代所有键 - keys()
console.log('\n2. 迭代所有键:');
for (const key of fruits.keys()) {
console.log('键:', key);
}
// 3. 迭代所有值 - values()
console.log('\n3. 迭代所有值:');
for (const value of fruits.values()) {
console.log('值:', value);
}
// 4. 迭代所有键值对 - entries()
console.log('\n4. 迭代所有键值对:');
for (const [key, value] of fruits.entries()) {
console.log(`${key}: ${value}`);
}
// 5. 直接迭代Map(默认迭代entries())
console.log('\n5. 直接迭代Map:');
for (const [key, value] of fruits) {
console.log(`${key}: ${value}`);
}
// 6. 转换为数组
console.log('\n6. 转换为数组:');
const fruitArray = Array.from(fruits);
console.log(fruitArray);
// 7. 使用扩展运算符转换为数组
console.log('\n7. 使用扩展运算符转换为数组:');
const fruitArray2 = [...fruits];
console.log(fruitArray2);
// 8. 数组解构
console.log('\n8. 数组解构:');
const [first, second, ...rest] = fruits;
console.log('第一个元素:', first);
console.log('第二个元素:', second);
console.log('剩余元素:', rest);运行结果:
1. 使用forEach迭代:
apple: 苹果
banana: 香蕉
orange: 橙子
grape: 葡萄2. 迭代所有键:
键: apple
键: banana
键: orange
键: grape3. 迭代所有值:
值: 苹果
值: 香蕉
值: 橙子
值: 葡萄4. 迭代所有键值对:
apple: 苹果
banana: 香蕉
orange: 橙子
grape: 葡萄5. 直接迭代Map:
apple: 苹果
banana: 香蕉
orange: 橙子
grape: 葡萄6. 转换为数组:
[ [ 'apple', '苹果' ], [ 'banana', '香蕉' ], [ 'orange', '橙子' ], [ 'grape', '葡萄' ] ]7. 使用扩展运算符转换为数组:
[ [ 'apple', '苹果' ], [ 'banana', '香蕉' ], [ 'orange', '橙子' ], [ 'grape', '葡萄' ] ]8. 数组解构:
第一个元素: [ 'apple', '苹果' ]
第二个元素: [ 'banana', '香蕉' ]
剩余元素: [ [ 'orange', '橙子' ], [ 'grape', '葡萄' ] ]
二、WeakMap集合类型
2.1 什么是WeakMap?
WeakMap是Map的特殊版本,它具有以下特点:
- 弱引用键:WeakMap的键只能是对象,并且是弱引用。当键对象没有其他引用时,会被垃圾回收机制回收,对应的键值对也会从WeakMap中自动移除。
- 不可枚举:WeakMap没有size属性,也不支持迭代方法(如keys()、values()、entries()),无法遍历其内容。
- 有限的方法:只支持get()、set()、has()、delete()四个方法。
弱引用概念:
- 强引用:普通的对象引用,会阻止垃圾回收
- 弱引用:不会阻止垃圾回收的引用
2.2 WeakMap的基本操作
// 创建WeakMap
const weakMap = new WeakMap();
// 创建几个对象作为键
const obj1 = { id: 1 };
const obj2 = { id: 2 };
const obj3 = { id: 3 };
// 添加键值对 - set()
weakMap.set(obj1, '对象1的数据');
weakMap.set(obj2, '对象2的数据');
weakMap.set(obj3, '对象3的数据');
// 获取值 - get()
console.log('获取obj1对应的值:', weakMap.get(obj1)); // 输出: 获取obj1对应的值: 对象1的数据
console.log('获取obj2对应的值:', weakMap.get(obj2)); // 输出: 获取obj2对应的值: 对象2的数据
// 判断键是否存在 - has()
console.log('是否包含obj1:', weakMap.has(obj1)); // 输出: 是否包含obj1: true
console.log('是否包含obj3:', weakMap.has(obj3)); // 输出: 是否包含obj3: true
// 删除键 - delete()
weakMap.delete(obj2);
console.log('删除obj2后是否存在:', weakMap.has(obj2)); // 输出: 删除obj2后是否存在: false
// WeakMap不支持size属性和迭代方法
console.log('WeakMap没有size属性:', weakMap.size); // 输出: WeakMap没有size属性: undefined
// 尝试迭代WeakMap会报错
try {
for (const item of weakMap) {
console.log(item);
}
} catch (e) {
console.log('迭代WeakMap报错:', e.message); // 输出: 迭代WeakMap报错: weakMap is not iterable
}运行结果:
获取obj1对应的值: 对象1的数据
获取obj2对应的值: 对象2的数据
是否包含obj1: true
是否包含obj3: true
删除obj2后是否存在: false
WeakMap没有size属性: undefined
迭代WeakMap报错: weakMap is not iterable
2.3 WeakMap的弱引用特性演示
弱引用是WeakMap最核心的特性,理解这一点对于正确使用WeakMap至关重要。
// 创建WeakMap
const weakMap = new WeakMap();
// 创建对象并添加到WeakMap
let obj = { data: '需要存储的数据' };
weakMap.set(obj, '这是obj的数据');
console.log('添加后是否存在:', weakMap.has(obj)); // 输出: 添加后是否存在: true
console.log('添加后的值:', weakMap.get(obj)); // 输出: 添加后的值: 这是obj的数据
// 移除对象的引用
obj = null;
// 手动触发垃圾回收(注意:在实际环境中无法保证立即执行)
// 以下代码仅在支持手动垃圾回收的环境中有效(如Chrome开发者工具)
if (typeof gc === 'function') {
console.log('\n手动触发垃圾回收...');
gc();
// 垃圾回收后检查
console.log('垃圾回收后是否存在:', weakMap.has(obj)); // 输出: 垃圾回收后是否存在: false
console.log('垃圾回收后的值:', weakMap.get(obj)); // 输出: 垃圾回收后的值: undefined
} else {
console.log('\n请在支持手动垃圾回收的环境中运行此示例(如Chrome开发者工具)');
console.log('提示:在Chrome中可勾选Settings > Experiments > Memory > Enable manual garbage collection');
}
// 另一个演示:临时对象自动回收
const tempObj = { temp: '临时数据' };
weakMap.set(tempObj, '临时对象的数据');
console.log('\n临时对象是否存在:', weakMap.has(tempObj)); // 输出: 临时对象是否存在: true
// 函数执行完毕后,tempObj将没有引用,会被垃圾回收运行结果:
添加后是否存在: true
添加后的值: 这是obj的数据手动触发垃圾回收...
垃圾回收后是否存在: false
垃圾回收后的值: undefined临时对象是否存在: true
注意:要看到垃圾回收效果,需要在支持手动垃圾回收的环境中运行(如Chrome开发者工具的Console中,并勾选"Enable manual garbage collection"选项)。
2.4 Map与WeakMap详细对比
| 特性 | Map | WeakMap |
|---|---|---|
| 键类型 | 任意类型 | 只能是对象 |
| 引用类型 | 强引用 | 弱引用 |
| 垃圾回收 | 键不会被自动回收 | 当键没有其他引用时会被自动回收 |
| 可枚举性 | 可枚举,支持迭代方法 | 不可枚举,不支持迭代 |
| size属性 | 有size属性 | 无size属性 |
| 可用方法 | set, get, has, delete, clear, keys, values, entries, forEach | set, get, has, delete |
| 内存泄漏风险 | 有(键是对象时) | 无 |
| 使用场景 | 需要遍历、需要存储基本类型键、需要知道大小 | 临时数据存储、私有数据、缓存 |
三、Map的实际应用场景
3.1 存储复杂数据结构
Map适合存储需要保持插入顺序、键类型多样的复杂数据结构。
// 使用Map存储用户信息,键可以是用户ID对象
const user1 = { id: 1 };
const user2 = { id: 2 };
const userData = new Map();
// 存储用户信息
userData.set(user1, {
name: '张三',
age: 30,
hobbies: ['阅读', '运动']
});
userData.set(user2, {
name: '李四',
age: 25,
hobbies: ['游戏', '音乐']
});
// 获取用户信息
console.log('用户1信息:', userData.get(user1));
console.log('用户2名称:', userData.get(user2).name);
// 遍历所有用户
console.log('\n所有用户信息:');
userData.forEach((data, user) => {
console.log(`用户ID: ${user.id}, 姓名: ${data.name}, 年龄: ${data.age}`);
});
// 检查用户是否存在
const user3 = { id: 3 };
console.log('\n用户3是否存在:', userData.has(user3)); // 输出: 用户3是否存在: false运行结果:
用户1信息: { name: '张三', age: 30, hobbies: [ '阅读', '运动' ] }
用户2名称: 李四所有用户信息:
用户ID: 1, 姓名: 张三, 年龄: 30
用户ID: 2, 姓名: 李四, 年龄: 25用户3是否存在: false
3.2 缓存计算结果
Map可以用于缓存函数计算结果,提高性能。
// 创建一个缓存Map
const calculationCache = new Map();
// 模拟一个耗时的计算函数
function expensiveCalculation(num) {
console.log(`执行耗时计算: ${num}`);
// 模拟计算耗时
let result = 0;
for (let i = 0; i < num * 1000000; i++) {
result += i;
}
return result;
}
// 使用缓存的计算函数
function calculateWithCache(num) {
// 如果缓存中存在,直接返回缓存结果
if (calculationCache.has(num)) {
console.log(`使用缓存结果: ${num}`);
return calculationCache.get(num);
}
// 否则执行计算并缓存结果
const result = expensiveCalculation(num);
calculationCache.set(num, result);
return result;
}
// 第一次计算(无缓存)
console.log('结果1:', calculateWithCache(10));
// 第二次计算相同的值(使用缓存)
console.log('结果2:', calculateWithCache(10));
// 计算另一个值(无缓存)
console.log('结果3:', calculateWithCache(15));
// 再次计算(使用缓存)
console.log('结果4:', calculateWithCache(10));
console.log('结果5:', calculateWithCache(15));
// 查看缓存大小
console.log('缓存大小:', calculationCache.size); // 输出: 缓存大小: 2运行结果:
执行耗时计算: 10
结果1: 499999500000
使用缓存结果: 10
结果2: 499999500000
执行耗时计算: 15
结果3: 1124999250000
使用缓存结果: 10
结果4: 499999500000
使用缓存结果: 15
结果5: 1124999250000
缓存大小: 2
3.3 实现多键映射
Map支持任意类型的键,可以实现多键映射的复杂逻辑。
// 创建一个多键映射的Map
const multiKeyMap = new Map();
// 定义几个对象作为键
const objKey = { id: 1 };
const funcKey = () => {};
const symbolKey = Symbol('unique');
// 添加多键映射
multiKeyMap.set(objKey, '对象键对应的值');
multiKeyMap.set(funcKey, '函数键对应的值');
multiKeyMap.set(symbolKey, 'Symbol键对应的值');
multiKeyMap.set(123, '数字键对应的值');
multiKeyMap.set('string', '字符串键对应的值');
// 获取值
console.log('对象键:', multiKeyMap.get(objKey)); // 输出: 对象键: 对象键对应的值
console.log('函数键:', multiKeyMap.get(funcKey)); // 输出: 函数键: 函数键对应的值
console.log('Symbol键:', multiKeyMap.get(symbolKey)); // 输出: Symbol键: Symbol键对应的值
// 演示对象键的引用特性
const anotherObj = { id: 1 }; // 与objKey内容相同但不是同一个对象
console.log('不同对象相同内容:', multiKeyMap.get(anotherObj)); // 输出: 不同对象相同内容: undefined
// 使用数组作为复合键
const compositeKey = ['user', 'settings', 'theme'];
multiKeyMap.set(compositeKey, 'dark');
console.log('复合键值:', multiKeyMap.get(compositeKey)); // 输出: 复合键值: dark
// 注意: 数组也是对象,必须使用同一个数组引用才能获取值
console.log('不同数组实例:', multiKeyMap.get(['user', 'settings', 'theme'])); // 输出: 不同数组实例: undefined运行结果:
对象键: 对象键对应的值
函数键: 函数键对应的值
Symbol键: Symbol键对应的值
不同对象相同内容: undefined
复合键值: dark
不同数组实例: undefined
四、WeakMap的实际应用场景
4.1 存储对象的私有数据
WeakMap可以安全地存储对象的私有数据,不会干扰垃圾回收。
// 创建一个WeakMap用于存储对象的私有数据
const privateData = new WeakMap();
// 定义一个类
class User {
constructor(name, age) {
// 公共属性
this.name = name;
// 使用WeakMap存储私有数据
privateData.set(this, {
age: age,
password: '默认密码',
loginCount: 0
});
}
// 公共方法可以访问私有数据
getAge() {
return privateData.get(this).age;
}
login(password) {
const data = privateData.get(this);
if (password === data.password) {
data.loginCount++;
return true;
}
return false;
}
getLoginCount() {
return privateData.get(this).loginCount;
}
// 修改私有数据
setPassword(newPassword) {
privateData.get(this).password = newPassword;
}
}
// 创建实例
const user = new User('张三', 30);
// 访问公共属性
console.log('用户名:', user.name); // 输出: 用户名: 张三
// 通过公共方法访问私有数据
console.log('年龄:', user.getAge()); // 输出: 年龄: 30
// 尝试直接访问私有数据(失败)
console.log('直接访问私有数据:', user.age); // 输出: 直接访问私有数据: undefined
// 登录功能
console.log('使用默认密码登录:', user.login('默认密码')); // 输出: 使用默认密码登录: true
console.log('登录次数:', user.getLoginCount()); // 输出: 登录次数: 1
// 修改密码
user.setPassword('newPassword123');
console.log('使用旧密码登录:', user.login('默认密码')); // 输出: 使用旧密码登录: false
console.log('使用新密码登录:', user.login('newPassword123')); // 输出: 使用新密码登录: true
console.log('登录次数:', user.getLoginCount()); // 输出: 登录次数: 2
// 当user实例被销毁时,privateData中的对应条目会自动被垃圾回收运行结果:
用户名: 张三
年龄: 30
直接访问私有数据: undefined
使用默认密码登录: true
登录次数: 1
使用旧密码登录: false
使用新密码登录: true
登录次数: 2
4.2 临时缓存对象数据
WeakMap适合存储临时缓存,当对象被回收时,缓存自动失效。
// 创建WeakMap作为缓存
const objectCache = new WeakMap();
// 获取对象数据的函数,带缓存功能
function getObjectData(obj) {
// 如果缓存中存在,直接返回
if (objectCache.has(obj)) {
console.log('使用缓存数据');
return objectCache.get(obj);
}
// 否则获取数据(模拟API请求或复杂计算)
console.log('获取新数据');
const data = {
timestamp: new Date().toISOString(),
randomValue: Math.random()
};
// 存入缓存
objectCache.set(obj, data);
return data;
}
// 创建测试对象
const obj1 = { id: 1 };
const obj2 = { id: 2 };
// 第一次获取(无缓存)
console.log('obj1数据1:', getObjectData(obj1));
// 第二次获取(有缓存)
console.log('obj1数据2:', getObjectData(obj1));
// 获取obj2数据
console.log('obj2数据1:', getObjectData(obj2));
// 清除obj1引用
obj1 = null;
// 手动触发垃圾回收(在支持的环境中)
if (typeof gc === 'function') {
console.log('\n触发垃圾回收...');
gc();
// 尝试获取已被回收的对象数据
console.log('obj1数据3:', getObjectData(obj1)); // 输出: obj1数据3: undefined
}
// obj2仍然存在,缓存有效
console.log('obj2数据2:', getObjectData(obj2));运行结果:
获取新数据
obj1数据1: { timestamp: '2023-11-15T08:30:00.000Z', randomValue: 0.123456789 }
使用缓存数据
obj1数据2: { timestamp: '2023-11-15T08:30:00.000Z', randomValue: 0.123456789 }
获取新数据
obj2数据1: { timestamp: '2023-11-15T08:30:01.000Z', randomValue: 0.987654321 }触发垃圾回收...
obj1数据3: undefined
使用缓存数据
obj2数据2: { timestamp: '2023-11-15T08:30:01.000Z', randomValue: 0.987654321 }
4.3 DOM元素元数据存储
WeakMap非常适合存储DOM元素的元数据,当DOM元素被移除时,相关数据会自动清理。
// 创建WeakMap存储DOM元素元数据
const elementMetadata = new WeakMap();
// 获取DOM元素
const button = document.createElement('button');
button.textContent = '点击我';
// 为DOM元素存储元数据
elementMetadata.set(button, {
clicks: 0,
created: new Date(),
lastClick: null
});
// 添加点击事件处理
button.addEventListener('click', function() {
// 获取元数据
const metadata = elementMetadata.get(this);
// 更新元数据
metadata.clicks++;
metadata.lastClick = new Date();
console.log(`按钮被点击了${metadata.clicks}次`);
console.log('最后点击时间:', metadata.lastClick.toLocaleTimeString());
});
// 添加到文档
document.body.appendChild(button);
// 模拟点击
console.log('模拟第一次点击:');
button.click();
console.log('\n模拟第二次点击:');
button.click();
// 一段时间后移除元素
setTimeout(() => {
console.log('\n移除按钮元素');
document.body.removeChild(button);
// 此时button元素没有引用了,元数据会随着垃圾回收自动清理
// 不需要手动从elementMetadata中删除
}, 2000);运行结果:
模拟第一次点击:
按钮被点击了1次
最后点击时间: 08:30:00模拟第二次点击:
按钮被点击了2次
最后点击时间: 08:30:00移除按钮元素
五、Map与WeakMap使用最佳实践
5.1 何时使用Map
- 需要迭代键值对时:当你需要遍历集合中的所有键或值时
- 需要知道集合大小时:当你需要使用size属性获取元素数量时
- 键不是对象类型时:当你需要使用字符串、数字等基本类型作为键时
- 需要长期存储数据时:当你不希望数据被自动删除时
- 需要清除所有元素时:当你需要使用clear()方法清空集合时
5.2 何时使用WeakMap
- 键是对象且需要自动回收时:当键对象不再使用时希望自动从集合中移除
- 存储对象的附加信息时:如存储对象的私有数据或元数据
- 实现临时缓存时:当对象被回收时,缓存自动失效
- 避免内存泄漏时:特别是在处理DOM元素或大型对象时
- 不需要迭代集合时:当你只需要通过键获取值,不需要遍历所有元素时
5.3 性能考量
- 内存占用:
- Map会保持所有键的强引用,可能导致内存占用增加
- WeakMap不会阻止垃圾回收,内存占用更优
- 访问速度:
- Map和WeakMap的get/set操作性能相近
- Map的迭代操作在大数据量时可能影响性能
- 垃圾回收:
- WeakMap有助于垃圾回收,适合临时数据
- Map需要手动管理内存,避免内存泄漏
5.4 常见错误和注意事项
将基本类型用作WeakMap的键:
const weakMap = new WeakMap();
weakMap.set('key', 'value'); // TypeError: Invalid value used as weak map key
期望WeakMap自动清理后能立即反映:
const weakMap = new WeakMap();
let obj = {};
weakMap.set(obj, 'data');
obj = null;
console.log(weakMap.has(obj)); // 可能仍然返回true,因为垃圾回收可能尚未执行
尝试迭代WeakMap:
const weakMap = new WeakMap();
for (const item of weakMap) { // TypeError: weakMap is not iterable
console.log(item);
}
混淆Map和对象的使用场景:
// 适合用对象的场景
const config = {
width: 100,
height: 200,
color: 'red'
};
// 适合用Map的场景
const userScores = new Map();
userScores.set(user1, 90);
userScores.set(user2, 85);六、总结
6.1 Map和WeakMap核心特性总结
| 特性 | Map | WeakMap |
|---|---|---|
| 键类型 | 任意类型 | 仅对象 |
| 引用方式 | 强引用 | 弱引用 |
| 自动清理 | 否 | 是(键对象无引用时) |
| 可迭代性 | 是 | 否 |
| size属性 | 有 | 无 |
| 主要方法 | set, get, has, delete, clear, keys, values, entries, forEach | set, get, has, delete |
| 内存泄漏风险 | 有 | 无 |
6.2 选择指南
- 优先使用对象的场景:
- 键是已知的字符串且数量有限
- 需要JSON序列化
- 需要使用对象字面量语法
- 需要继承原型链方法
- 优先使用Map的场景:
- 键类型多样(不只是字符串)
- 需要保持插入顺序
- 需要频繁添加/删除键值对
- 需要迭代或获取大小
- 优先使用WeakMap的场景:
- 键是对象且可能被回收
- 存储对象的私有数据
- 实现临时缓存
- 避免内存泄漏
6.3 现代JavaScript开发中的应用趋势
随着JavaScript的发展,Map和WeakMap在现代开发中的应用越来越广泛:
- 框架内部实现:React、Vue等框架大量使用WeakMap存储组件元数据
- 状态管理:复杂状态管理中使用Map存储动态键值对
- 工具库开发:许多实用工具库使用WeakMap实现无侵入式扩展
- 性能优化:通过WeakMap实现高效的缓存机制
- 私有属性模拟:在ES私有字段提案之前,WeakMap是模拟私有属性的主要方式
Map和WeakMap是JavaScript中强大的集合类型,理解它们的特性和适用场景对于编写高效、安全的代码至关重要。合理使用这些数据结构可以提高代码的可读性、性能和可维护性,特别是在处理复杂数据关系和内存管理时。
到此这篇关于JavaScript集合Map与WeakMap使用最佳实践的文章就介绍到这了,更多相关js map与weakmap内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
