前端进阶之旅前端进阶之旅
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
  • 基础篇HTML/CSS/JS 打底
  • 进阶篇原理与工程化
  • 高频篇面试最常问的那批
  • 精选篇按模块收敛的总结
  • 手写篇常考代码手写实现
  • 面经篇真实面试问题复盘
  • AI 篇NEWAI 时代的前端考点
  • 历年面经NEW按年份追踪真实考点
  • 每日一题每天一道,攒手感
  • 专项自测100 题快速查漏
  • 小程序题库小程序专项刷题
  • 算法题库NEW在线编码即时判题
  • 知识卡片NEW碎片时间过考点
  • 面试题大全常见问题解析
  • AI 答疑NEW随时提问,即时解析
  • AI 模拟面试NEW模拟真实面试 + 报告
  • AI 定制路线NEW按你的简历现排
  • AI 知识地图NEW串起全站知识点
  • 原理篇React / Vue 源码拆解
  • HTTP从报文一路讲到 HTTPS
  • 浏览器渲染、事件循环、进程
  • 计算机基础Linux、网络、操作系统
  • 设计模式23 种模式怎么用
  • Node学习指南从环境搭建到服务端
  • NPM工作流script、依赖与发布
  • Docker容器化部署上手
  • Canvas图形与动画实战
  • 前端系统进阶学习大型项目工程化
  • 前端综合文章长期沉淀的实践文
  • 思维导图知识点全景图
  • 学习路线按图索骥不跑偏
  • AI 热点NEWAI 每日动态
  • 公众号动态公众号历史文章
  • 博客动态站长的技术博客
  • 开发者导航常用工具与文档站
首页程序员面试题库Set SameValueZero 唯一性 NaN +0 -0
JSJavaScript数组与集合

Set 怎样判重,为什么 NaN 会合并而两个相同对象不会?

先定义业务眼中的“同一个”,再判断 Set 的相等规则是否刚好符合它。

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

对照三种相等判断

  1. 严格相等NaN 不等于自身,正负零相等
  2. Object.isNaN 等于自身,正负零可区分
  3. SameValueZeroNaN 与正负零都适合合并为同一成员

Set 的判重规则由语言定义,不能给构造器传一个自定义比较函数来改成按业务字段比较。

核心回答

先记住这个答案

Set 用 SameValueZero 判断成员是否重复:NaN 与 NaN 相同,正零和负零相同,其他情况与严格相等的主要行为一致,不做类型转换,对象按身份比较。因此一和字符串一可同时存在,两个结构相同的新对象也不会自动合并。现代规范还会把加入集合的负零归一化为正零,不能说遍历时保留先插入的负号。数组可以通过 [...new Set(values)] 去重,但若需要按用户编号、结构内容或业务状态合并,应先设计明确键和冲突策略。

  • NaN 合并,正负零合并且规范化为正零
  • 数值与字符串不会被隐式转换后去重
  • 对象相同内容不等于相同身份

用特殊数值和对象身份验证规则

下面同时加入两次 NaN、正负零、数值一与字符串一,以及一个对象的重复引用和结构相同的新对象。输出数量可以检验每一组到底被合并还是保留,避免只背算法名称。

负零另做一次身份判断:即使只加入负零,迭代出来的值也按现代规范归一化为正零。这个细节与“重复值不改变插入顺序”是两条规则,不能用后者推导保留零的原始符号。

验证去重、对象引用与负零JavaScript
const item = { id: 7 };
const unique = [...new Set([NaN, NaN, -0, +0, 1, '1', item, item, { id: 7 }])];
console.log(unique.length);
console.log(new Set([1, '1']).size);
console.log(new Set([item, { id: 7 }]).size);
const zero = [...new Set([-0])][0];
console.log(Object.is(zero, -0), Object.is(zero, +0));
查看输出与解释
6
2
2
false true

总共六类成员;字符串一与数值一分开,两个独立对象也分开。最后两个布尔值证明负零已经归一化,不能依靠 Set 的遍历结果保留输入零的方向信息。

数组去重会丢掉重复次数的信息

标签列表若只关心不同标签,直接 Set 去重再展开通常符合需求,而且保留首次成功插入的成员顺序。若输入里重复次数代表点击量、错误频率或库存数量,去重就会删除业务需要的信息。

对象列表按编号合并时,可以用 Map 以经过校验的业务编号为键,但还必须决定重复编号保留第一条、最后一条还是报冲突。把对象 JSON.stringify 后当键也有表示限制,不能当作通用结构相等算法。

序列化与归一化要在边界上说明

JSON 往返会改变某些值的表示,例如数组中的 NaN 会变成 null,不能期待反序列化后仍保有相同的判重含义。需要跨网络保留特殊值时,应定义显式编码与解码规则。

如果业务确实区分正零和负零,必须在加入集合前保留单独标记或使用其他键表示。性能方面规范要求平均访问时间优于线性,并未强制所有实现都是哈希表或保证每次操作恒定时间。

回答前,多想一步

容易答错的地方

认为先加入负零就会保留负号
现代集合键处理会归一化负零,因此遍历结果不能保留这个差别。需要符号信息时应在入集合前编码或单独记录,不能事后从已经合并的成员里恢复原始输入。
认为对象去重自动比较全部字段
Set 比较对象身份,两个独立创建的相同结构对象仍是两个成员。按业务字段去重应明确字段合法性、键冲突和保留策略,避免把身份判重误用成数据合并规则。
试着用自己的话回答

面试官还会怎么问?

Set 可以区分一和字符串一吗?

可以,SameValueZero 不执行隐式类型转换,类型不同的这两个值是不同成员。若业务希望把它们归为同一编号,应先按受控规则规范化输入,并处理无效字符串等边界。

同一对象改了字段后还是同一成员吗?

是,对象身份没有改变,Set 仍能用这个引用找到它。若业务判重依据可变字段,Set 无法自动重建这套关系;应选取稳定键或明确更新索引,避免字段变化后查找语义失真。

Set 去重后的顺序能直接当排序吗?

它保留成员首次成功加入的顺序,既不是按数值也不是按字典排序。需要排序应显式选择比较规则,再决定先去重还是先排序,以及重复记录应该保留哪份业务信息。

从一道题,走向一组知识

把知识连起来

数组与集合

数组 includes 和 indexOf 能检测 NaN 吗?两者在语义上有哪些差异?

对照数组查找 NaN 时采用的相等判断差异。

数组与集合

数组展开为什么能复制数组和 Set,它到底读取了什么?

理解把 Set 成员转换成数组的迭代过程。

放回知识体系中复习JavaScript 进阶知识

参考资料

  • MDN:Set
  • ECMAScript:Set.prototype.add

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

本题目录
  1. 先记住这个答案
  2. 用特殊数值和对象身份验证规则
  3. 数组去重会丢掉重复次数的信息
  4. 序列化与归一化要在边界上说明
  5. 容易答错的地方
  6. 面试官还会怎么问
  7. 把知识连起来
读懂,再试着讲出来

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

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