核心概念与基础用法
Set
- 定义:存储唯一、无序的值的集合 (不能有重复元素),键和值是同一个 (可以理解为 “值的集合”);
- 核心特性:
- 元素唯一:自动去重 (判断重复的规则和 === 一致,仅 NaN 例外,Set 认为 NaN 等于 NaN);
- 强引用:存储的元素会被强引用,即使外部没有引用,Set 仍会保留,不会被垃圾回收 (GC);
- 可遍历:支持 forEach、for…of 等遍历方式;
- 基础用法:
// 创建 Set const s = new Set(); // 添加元素 s.add(1); s.add(2); s.add(2); // 重复元素,不会被添加 s.add(NaN); s.add(NaN); // NaN 视为同一个 // 常用方法 console.log(s.size); // 3 (1, 2, NaN) console.log(s.has(1)); // true s.delete(2); // 删除元素 s.clear(); // 清空 // 遍历 const s2 = new Set([1, 2, 3]); for (let val of s2) { console.log(val); // 1 2 3 } s2.forEach(val => console.log(val));
WeakSet
-
定义:存储唯一、弱引用的对象的集合 (只能存对象,不能存原始值);
-
核心特性:
- 仅存对象:不能存储数字、字符串等原始值,否则报错;
- 弱引用:存储的对象如果没有其他强引用,会被 GC 自动回收,WeakSet 不会阻止回收;
- 不可遍历:没有 size 属性,不支持 forEach/for…of,也没有 clear 方法 (无法获取内部元素);
-
基础用法:
const ws = new WeakSet(); let obj = { name: "test" }; // 添加对象 ws.add(obj); console.log(ws.has(obj)); // true // 不能添加原始值 // ws.add(1); // 报错:Invalid value used in weak set // 解除强引用,obj 会被 GC 回收,WeakSet 中也会消失 obj = null; console.log(ws.has(obj)); // false
Map
-
定义:存储键值对的集合 (键可以是任意类型,包括对象、函数等,解决了普通对象键只能是字符串 / 符号的问题);
-
核心特性:
- 键任意类型:键可以是对象、函数、原始值,判断键重复的规则和 Set 一致;
- 强引用:键和值都会被强引用,不会被 GC 回收;
- 可遍历:支持 forEach、for…of,可通过 entries()/keys()/values() 遍历;
-
基础用法:
// 创建 Map const m = new Map(); const keyObj = { id: 1 }; // 添加键值对 m.set(keyObj, "hello"); m.set(1, "world"); m.set(NaN, "test"); // 常用方法 console.log(m.size); // 3 console.log(m.get(keyObj)); // "hello" console.log(m.has(NaN)); // true m.delete(1); // 遍历 for (let [key, value] of m) { console.log(`${key}: ${value}`); } m.forEach((value, key) => console.log(`${key}: ${value}`));
WeakMap
-
定义:存储键为弱引用对象、值任意类型的键值对集合;
-
核心特性:
- 键必须是对象:键不能是原始值,否则报错;
- 键是弱引用:如果键对象没有其他强引用,会被 GC 回收,对应的键值对也会从 WeakMap 中消失;
- 不可遍历:没有 size 属性,不支持遍历,也没有 clear 方法;
-
基础用法:
const wm = new WeakMap(); let keyObj = { id: 2 }; // 添加键值对 wm.set(keyObj, "weak map value"); console.log(wm.get(keyObj)); // "weak map value" // 不能用原始值当键 // wm.set(1, "error"); // 报错:Invalid value used as weak map key // 解除强引用,键对象被 GC 回收,WeakMap 中对应的键值对消失 keyObj = null; console.log(wm.get(keyObj)); // undefined
核心差异对比
| 特性 | Set | WeakSet | Map | WeakMap |
|---|---|---|---|---|
| 存储类型 | 唯一值 | 唯一对象 | 键值对 | 键值对 (键为对象) |
| 元素 / 键类型 | 任意类型 | 仅对象 | 键任意类型 | 键仅对象 |
| 引用类型 | 强引用 | 弱引用 | 强引用 | 键弱引用 |
| 是否可遍历 | 是 | 否 | 是 | 否 |
| 是否有 size 属性 | 是 | 否 | 是 | 否 |
| 常用方法 | add/has/delete/clear/forEach | add/has/delete | set/get/has/delete/clear/forEach | set/get/has/delete |
典型使用场景
-
Set:
- 数组去重 ([…new Set(arr)]);
- 存储不重复的标识 (如用户 ID 列表);
- 配合 Array.from 或扩展运算符实现集合运算 (交集、并集、差集);
-
WeakSet:
- 存储临时对象 (如 DOM 元素,当 DOM 被移除时自动清理);
- 标记对象状态 (如标记 “已处理的对象”,无需手动清理);
-
Map:
- 存储键为对象的键值对 (如以 DOM 元素为键存储对应数据);
- 需要有序遍历键值对的场景;
- 键的类型不固定 (如数字、对象混合作为键);
-
WeakMap:
- 为对象附加私有数据 (不会导致内存泄漏);
- 缓存计算结果 (键对象销毁时,缓存自动清理);
- DOM 元素相关数据存储 (DOM 移除时自动释放内存);
面试题
Set 和 Array 的区别?什么时候用 Set 而不是 Array?
-
核心区别:
- 唯一性:Set 自动去重,Array 允许重复元素;
- 有序性:Set 是无序集合 (ES6 实际按插入顺序遍历,但无索引),Array 是有序数组 (有索引);
- 方法差异:Set 没有 push/pop/splice 等数组方法,而是 add/delete/has;Array 不能直接判断元素是否存在 (需用 includes),Set 用 has 更高效;
-
使用 Set 的场景:
- 需要快速去重 (如 […new Set(arr)] 是数组去重的最优解之一);
- 需要频繁判断 “元素是否存在” (Set.prototype.has 的时间复杂度是 O (1),Array.prototype.includes 是 O (n));
- 无需通过索引访问元素的场景;
WeakSet/WeakMap 的 “弱引用” 是什么意思?和 Set/Map 的强引用有什么区别?
-
弱引用核心定义:弱引用不会阻止 JavaScript 垃圾回收机制 (GC) 回收对象。如果一个对象仅被 WeakSet/WeakMap 引用 (无其他强引用),GC 会自动回收该对象,并从 WeakSet/WeakMap 中移除对应的元素 / 键值对;
-
强引用 vs 弱引用对比:
// 1. Set 强引用示例:即使外部解除引用,Set 仍保留,导致内存泄漏 const set = new Set(); let obj1 = { name: "test" }; set.add(obj1); obj1 = null; // 解除外部强引用 console.log([...set][0]); // { name: "test" } —— Set 仍强引用,obj1 未被 GC 回收// 2. WeakSet 弱引用示例:外部解除引用后,对象被 GC 回收 const weakSet = new WeakSet(); let obj2 = { name: "test2" }; weakSet.add(obj2); obj2 = null; // 解除外部强引用 console.log(weakSet.has(obj2)); // false —— obj2 被 GC 回收,WeakSet 中也消失 -
考点:弱引用的核心价值是避免内存泄漏,适合存储临时对象 (如 DOM 元素、临时缓存);
为什么 WeakSet/WeakMap 不能遍历?没有 size 属性?
核心原因是弱引用的不确定性:
-
WeakSet/WeakMap 中的元素 / 键可能随时被 GC 回收 (外部强引用消失时),导致其内部数据是 “动态变化” 的;
-
如果支持遍历或 size 属性,遍历过程中数据可能突然消失,导致结果不准确;
-
为了保证语言的一致性和安全性,ES6 设计时直接禁止了 WeakSet/WeakMap 的遍历和 size 属性;
用 WeakMap 实现一个 “对象缓存”,要求对象销毁时缓存自动清理
// 实现一个基于 WeakMap 的缓存工具
const cache = new WeakMap();
// 模拟耗时计算函数
function calculate(obj) {
// 先查缓存,有则直接返回
if (cache.has(obj)) {
console.log("从缓存获取结果");
return cache.get(obj);
}
// 无缓存则计算,并存入 WeakMap
console.log("首次计算");
const result = obj.id * 100; // 模拟耗时计算
cache.set(obj, result);
return result;
}
// 测试
let user = { id: 10 };
console.log(calculate(user)); // 首次计算 → 1000
console.log(calculate(user)); // 从缓存获取结果 → 1000
// 销毁对象:解除强引用,GC 会回收 user,WeakMap 中的缓存也会自动清理
user = null;
console.log(cache.has(user)); // false
如何用 Set 实现数组的交集、并集、差集?
const arr1 = [1, 2, 3, 4];
const arr2 = [3, 4, 5, 6];
// 1. 并集(合并去重)
const union = [...new Set([...arr1, ...arr2])];
console.log(union); // [1,2,3,4,5,6]
// 2. 交集(两个数组都有的元素)
const set1 = new Set(arr1);
const intersection = arr2.filter(item => set1.has(item));
console.log(intersection); // [3,4]
// 3. 差集(arr1 有但 arr2 没有的元素)
const set2 = new Set(arr2);
const difference = arr1.filter(item => !set2.has(item));
console.log(difference); // [1,2]
为什么 WeakMap 适合做私有属性?
-
核心原因:WeakMap 的键是对象,且是弱引用,外部无法访问 WeakMap 内部的键值对,同时不会导致内存泄漏;
-
示例 (模拟类的私有属性):
// 用 WeakMap 存储私有属性,外部无法访问 const privateData = new WeakMap(); class Person { constructor(name, age) { // 私有属性存在 WeakMap 中,键是实例本身 privateData.set(this, { name, age }); } getName() { // 只有类内部能通过 this 访问私有属性 return privateData.get(this).name; } } const p = new Person("张三", 20); console.log(p.getName()); // 张三 console.log(privateData.get(p)); // { name: "张三", age: 20 }(如果能访问 privateData) // 外部无法直接访问 p 的 name/age,且当 p 被销毁时,privateData 中的对应数据也会被 GC 回收
🌿 reset、revert、rebase 解析
上一篇