前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
知识卡片
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
  • AI 助手随时提问,即时解析
  • AI 模拟面试模拟真实面试 + 报告
  • AI 知识地图串起全站知识点
  • AI 定制路线按你的简历现排
AI 热点
旧版
首页程序员面试题库WeakSet 与 Set 引用遍历标记对象
JavaScript数组与集合

WeakSet 适合用来标记对象已处理吗,它和 Set 有什么不同?

WeakSet 适合回答「这个对象处理过没有」,不适合回答「处理过的都有哪些」。它只能存对象和非注册 Symbol,不会阻止成员被垃圾回收,所以没有 size,也不能遍历。Set 能存任意值、能遍历计数,但会一直引用着成员。

前端进阶之旅 · 一题精讲更新于 2026.09.29
JavaScript#数组与集合
先看核心答案读代码示例
理解线索

`WeakSet` 和 `Set` 的三处差别

  1. 成员类型WeakSet 只收对象和非注册 Symbol,Set 什么都收
  2. 引用强弱WeakSet 不阻止成员被回收,Set 会一直保留成员
  3. 能不能遍历WeakSet 只有 add、has、delete 三个方法,而 Set 能遍历和计数

标记「开始处理」还是「处理成功」要想清楚。开始时就 add,处理失败的对象以后就再也进不来了。

核心回答

先记住这个答案

适合。WeakSet 的用途就是给对象打标记:add(obj) 表示标记过,has(obj) 查询有没有标记。它对成员是弱引用,对象在别处没有引用后,可以被正常回收,标记也跟着消失,不会造成内存泄漏。代价是它不能遍历,没有 size,只能存对象和非注册 Symbol。Set 正好相反:能存任意值,能遍历和计数,但会一直引用着对象,对象用完了还得手动 delete。所以只需要「这个对象有没有被处理过」时用 WeakSet,需要列出或统计所有成员时用 Set。

  • 按对象引用打标记,字段相同的新对象不算
  • 不会阻止对象被回收,标记随对象一起消失
  • 没有 size 也不能遍历,只能 has 已知对象
  • 深拷贝和递归遍历里常用来防止循环引用

标记对象,避免重复处理

写一个只处理一次的函数,同一个对象第二次传进来就跳过。

按引用判断是否处理过JavaScript
const handled = new WeakSet()
let count = 0

function handleOnce(record) {
  if (handled.has(record)) return false   // 已经处理过
  handled.add(record)
  count++
  return true
}

const first = { id: 7 }
console.log(handleOnce(first))
console.log(handleOnce(first))            // 同一个对象,跳过
console.log(handleOnce({ id: 7 }))        // 字段一样但是新对象,照常处理
console.log(count)
查看输出与解释
true
false
true
2

第二次传入 first 被跳过了。第三次传入的对象 id 也是 7,但它是一个新对象,引用不同,所以照常处理。WeakSet 认的是对象本身,不看字段内容。如果要按业务 id 去重,就要用 Set 存 id。 这个例子里处理是同步的,不会失败,所以一开始就 add 没问题。真实场景里如果处理可能失败,应该在成功之后再 add,否则失败的对象以后就没机会重试了。

递归遍历时防止循环引用

写深拷贝、深度比较或者遍历一个对象树时,最怕遇到循环引用,比如 a.self = a,递归会一直进行下去直到栈溢出。常见做法是进入一个对象前先 has 检查,没见过就 add 再递归。

「全程访问过」和「当前路径上」不一样
用一个从不删除的 WeakSet 记录所有访问过的对象,能保证每个对象只处理一次。但它不能用来判断「有环」:两个父节点共享同一个子节点时,第二次遇到这个子节点也会命中,而这不是环。 真正检测环,要记录当前递归路径:进入对象时 add,处理完它的所有子节点后 delete。只有在当前路径上再次遇到同一个对象,才说明有环。深拷贝要把已经拷贝过的对象和副本对应起来,这时用 WeakMap 更合适,键是原对象,值是副本。
回答前,多想一步

容易答错的地方

把 WeakSet 当成可以遍历的弱数组
WeakSet 只有 add、has、delete 三个方法,没有 size,不能 for...of,也不能展开。需要展示或统计所有被标记的对象,要用 Set 或数组,并自己负责清理。
以为用 Set 标记对象一定会内存泄漏
不一定。如果 Set 本身是个局部变量,函数结束后整个 Set 都会被回收,里面的对象也能释放。泄漏发生在 Set 长期存活、对象用完了又没删掉的时候,比如挂在模块顶层的缓存。
试着用自己的话回答

面试官还会怎么问?

WeakSet 里能存数字 id 吗?

不能,add(1) 会抛 TypeError。数字、字符串这些原始值不会被垃圾回收,放进 WeakSet 没有意义。按 id 标记用 Set。

标记后处理失败,怎样允许重试?

两种做法:要么处理成功后再 add;要么开始时 add,失败时在 catch 里 delete。如果还要区分「处理中」和「已完成」,一个布尔标记不够用,改用 WeakMap 存状态,比如 'pending' 和 'done'。

类里怎么用 WeakSet 做品牌检查?

在构造函数里把 this 加进一个模块内部的 WeakSet,方法里先 has(this) 检查,就能确认调用者是这个类真正创建的实例,而不是伪造的对象。现在也可以用私有字段加 #field in obj 语法做同样的检查。

从一道题,走向一组知识

把知识连起来

数组与集合

WeakMap 的键只能是对象吗,它怎样影响垃圾回收?

除了「处理过没有」还要记录处理结果或阶段时,用 WeakMap 存值。

数组与集合

Set 是怎么判断重复的,为什么 NaN 能去重而两个相同的对象不行?

Set 和 WeakSet 对对象成员都按引用判断,那篇讲 Set 完整的判重规则。

参考资料

  • MDN:WeakSet

示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。

本题目录
  1. 先记住这个答案
  2. 标记对象,避免重复处理
  3. 递归遍历时防止循环引用
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

先看核心答案,再读代码。最后展开追问,检查自己有没有遗漏边界。

试着回答追问
浏览全部面试题理解原理,也关注真实的使用场景。回到顶部 ↑