前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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 热点
旧版
首页程序员面试题库Set SameValueZero 唯一性 NaN +0 -0
JavaScript数组与集合

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

Set 用 SameValueZero 规则判断两个值是否重复。它和 === 基本一样,只有一点不同:NaN 被当成等于 NaN,所以能去重。对象按引用比较,两个字段完全一样的对象也是两个不同的成员。

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

三种相等规则对照

  1. ===NaN 不等于自己,+0 等于 -0
  2. Object.isNaN 等于自己,+0 不等于 -0
  3. SameValueZeroNaN 等于自己,+0 等于 -0,这是 Set 和 includes 用的规则

Set 不能传自定义比较函数。想按对象的某个字段去重,只能自己挑一个键,比如用 Map 以 id 为键。

核心回答

先记住这个答案

Set 判断两个值是否重复用的是 SameValueZero 规则。它和 === 几乎一样:不做类型转换,对象按引用比较。唯一的区别是 NaN 被当成等于 NaN,所以 new Set([NaN, NaN]) 只有一个成员,而 NaN === NaN 是 false。+0 和 -0 被当成同一个值,而且存进 Set 的 -0 会被统一成 +0。所以数组去重可以直接写 [...new Set(arr)],但对象数组里两个字段一样的对象不会被去掉,要按字段去重得自己选一个键。

  • NaN 能去重,+0 和 -0 算同一个值
  • 不做类型转换,1 和 "1" 是两个成员
  • 对象按引用比较,内容相同也不算重复
  • 对象数组按字段去重,用 Map 以 id 为键

用特殊值和对象验证规则

把 NaN、正负零、数字和字符串、同一个对象和字段相同的新对象放在一起去重,看最后剩几个。

去重结果和负零JavaScript
const item = { id: 7 }
const unique = [...new Set([NaN, NaN, -0, +0, 1, '1', item, item, { id: 7 }])]
console.log(unique.length)                      // NaN、0、1、'1'、item、新对象

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))  // -0 被统一成 +0
查看输出与解释
6
2
2
false true

9 个值去重后剩 6 个:两个 NaN 合成一个,-0 和 +0 合成一个,同一个 item 引用合成一个。数字 1 和字符串 '1' 类型不同,都保留。最后那个 { id: 7 } 虽然和 item 字段一样,但它是新创建的对象,引用不同,所以也保留。 最后一行说明即使只往 Set 里放一个 -0,取出来的也是 +0。规范规定 add 时会把 -0 转成 +0。

对象数组怎么按字段去重

接口返回的用户列表里同一个用户出现了两次,两条记录是不同的对象,Set 去不掉。这时要选一个能代表「同一个用户」的字段,通常是 id。

用 Map 以 id 为键
写成 [...new Map(users.map(u => [u.id, u])).values()]。Map 的键是 id,同一个 id 后面的记录会覆盖前面的值,所以最后保留的是每个 id 最后出现的那条。如果要保留第一条,就在循环里先判断 map.has(u.id),存在就跳过。 有人会用 JSON.stringify 把对象转成字符串再放进 Set 去重,但字段顺序不同就会被当成两个值,值里有 undefined 或函数还会丢失,不是可靠的做法。
回答前,多想一步

容易答错的地方

说 Set 用 === 判断重复
如果用的是 ===,那么 new Set([NaN, NaN]) 就会有两个成员,因为 NaN === NaN 是 false。实际只有一个,所以准确的说法是 SameValueZero。
说 Set 用 Object.is 判断重复
Object.is(+0, -0) 是 false,如果 Set 用它,+0 和 -0 会是两个成员。实际上它们被当成同一个,这正是 SameValueZero 名字里 Zero 的意思。
试着用自己的话回答

面试官还会怎么问?

去重之后的顺序是什么?

保留每个值第一次出现的位置。[...new Set([3, 1, 3, 2])] 得到 [3, 1, 2],不会自动排序,要排序就再调用 sort。

对象放进 Set 后改了字段,还是同一个成员吗?

是。Set 记住的是引用,改字段不改引用,set.has(obj) 仍然是 true。也正因为这样,不能指望 Set 按字段内容帮你去重。

数组 includes 和 Set.has 判断相等的规则一样吗?

一样,都是 SameValueZero,都能找到 NaN。indexOf 不一样,它用 ===,所以 [NaN].indexOf(NaN) 返回 -1。

从一道题,走向一组知识

把知识连起来

数组与集合

数组的 includes 和 indexOf 能找到 NaN 吗?两者还有哪些差别?

includes 和 Set 一样用 SameValueZero,能找到 NaN,而 indexOf 用 === 找不到。

数组与集合

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

[...new Set(arr)] 去重的后半步靠展开运算符消费 Set 的迭代器,那篇讲这一步做了什么。

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

参考资料

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

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

本题目录
  1. 先记住这个答案
  2. 用特殊值和对象验证规则
  3. 对象数组怎么按字段去重
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

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

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