核心概念与基础用法

Set

  1. 定义:存储唯一、无序的值的集合 (不能有重复元素),键和值是同一个 (可以理解为 “值的集合”)
  2. 核心特性:
    1. 元素唯一:自动去重 (判断重复的规则和 === 一致,仅 NaN 例外,Set 认为 NaN 等于 NaN)
    2. 强引用:存储的元素会被强引用,即使外部没有引用,Set 仍会保留,不会被垃圾回收 (GC)
    3. 可遍历:支持 forEach、for…of 等遍历方式;
  3. 基础用法:
    // 创建 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

  1. 定义:存储唯一、弱引用的对象的集合 (只能存对象,不能存原始值)

  2. 核心特性:

    1. 仅存对象:不能存储数字、字符串等原始值,否则报错;
    2. 弱引用:存储的对象如果没有其他强引用,会被 GC 自动回收,WeakSet 不会阻止回收;
    3. 不可遍历:没有 size 属性,不支持 forEach/for…of,也没有 clear 方法 (无法获取内部元素)
  3. 基础用法:

    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

  1. 定义:存储键值对的集合 (键可以是任意类型,包括对象、函数等,解决了普通对象键只能是字符串 / 符号的问题)

  2. 核心特性:

    1. 键任意类型:键可以是对象、函数、原始值,判断键重复的规则和 Set 一致;
    2. 强引用:键和值都会被强引用,不会被 GC 回收;
    3. 可遍历:支持 forEach、for…of,可通过 entries()/keys()/values() 遍历;
  3. 基础用法:

    // 创建 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

  1. 定义:存储键为弱引用对象、值任意类型的键值对集合;

  2. 核心特性:

    1. 键必须是对象:键不能是原始值,否则报错;
    2. 键是弱引用:如果键对象没有其他强引用,会被 GC 回收,对应的键值对也会从 WeakMap 中消失;
    3. 不可遍历:没有 size 属性,不支持遍历,也没有 clear 方法;
  3. 基础用法:

    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

典型使用场景

  1. Set

    1. 数组去重 ([…new Set(arr)])
    2. 存储不重复的标识 (如用户 ID 列表)
    3. 配合 Array.from 或扩展运算符实现集合运算 (交集、并集、差集)
  2. WeakSet

    1. 存储临时对象 (如 DOM 元素,当 DOM 被移除时自动清理)
    2. 标记对象状态 (如标记 “已处理的对象”,无需手动清理)
  3. Map

    1. 存储键为对象的键值对 (如以 DOM 元素为键存储对应数据)
    2. 需要有序遍历键值对的场景;
    3. 键的类型不固定 (如数字、对象混合作为键)
  4. WeakMap

    1. 为对象附加私有数据 (不会导致内存泄漏)
    2. 缓存计算结果 (键对象销毁时,缓存自动清理)
    3. DOM 元素相关数据存储 (DOM 移除时自动释放内存)

面试题

Set 和 Array 的区别?什么时候用 Set 而不是 Array?

  1. 核心区别:

    1. 唯一性:Set 自动去重,Array 允许重复元素;
    2. 有序性:Set 是无序集合 (ES6 实际按插入顺序遍历,但无索引)Array 是有序数组 (有索引)
    3. 方法差异:Set 没有 push/pop/splice 等数组方法,而是 add/delete/hasArray 不能直接判断元素是否存在 (需用 includes)Sethas 更高效;
  2. 使用 Set 的场景:

    1. 需要快速去重 (如 […new Set(arr)] 是数组去重的最优解之一)
    2. 需要频繁判断 “元素是否存在” (Set.prototype.has 的时间复杂度是 O (1),Array.prototype.includes 是 O (n))
    3. 无需通过索引访问元素的场景;

WeakSet/WeakMap 的 “弱引用” 是什么意思?和 Set/Map 的强引用有什么区别?

  1. 弱引用核心定义:弱引用不会阻止 JavaScript 垃圾回收机制 (GC) 回收对象。如果一个对象仅被 WeakSet/WeakMap 引用 (无其他强引用)GC 会自动回收该对象,并从 WeakSet/WeakMap 中移除对应的元素 / 键值对;

  2. 强引用 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 中也消失
    
  3. 考点:弱引用的核心价值是避免内存泄漏,适合存储临时对象 (如 DOM 元素、临时缓存)

为什么 WeakSet/WeakMap 不能遍历?没有 size 属性?

核心原因是弱引用的不确定性:

  1. WeakSet/WeakMap 中的元素 / 键可能随时被 GC 回收 (外部强引用消失时),导致其内部数据是 “动态变化” 的;

  2. 如果支持遍历或 size 属性,遍历过程中数据可能突然消失,导致结果不准确;

  3. 为了保证语言的一致性和安全性,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 适合做私有属性?

  1. 核心原因:WeakMap 的键是对象,且是弱引用,外部无法访问 WeakMap 内部的键值对,同时不会导致内存泄漏;

  2. 示例 (模拟类的私有属性)

    // 用 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 回收
    
打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

你好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 核心概念与基础用法
    1. 1.1. Set
    2. 1.2. WeakSet
    3. 1.3. Map
    4. 1.4. WeakMap
  2. 2. 核心差异对比
  3. 3. 典型使用场景
  4. 4. 面试题
    1. 4.1. Set 和 Array 的区别?什么时候用 Set 而不是 Array?
    2. 4.2. WeakSet/WeakMap 的 “弱引用” 是什么意思?和 Set/Map 的强引用有什么区别?
    3. 4.3. 为什么 WeakSet/WeakMap 不能遍历?没有 size 属性?
    4. 4.4. 用 WeakMap 实现一个 “对象缓存”,要求对象销毁时缓存自动清理
    5. 4.5. 如何用 Set 实现数组的交集、并集、差集?
    6. 4.6. 为什么 WeakMap 适合做私有属性?