前端进阶之旅前端进阶之旅
基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
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 与数组元素存在性检查性能
JavaScript数组与集合

频繁判断元素是否存在时,Set.has 一定比 includes 快吗?

includes 要从头挨个比较,是 O(n)。而 Set.has 在常见实现里是哈希查找,接近 O(1)。但建一个 Set 本身要把数组遍历一遍,所以只有同一批数据要查很多次时,先建 Set 再查才划算。

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

值不值得建 `Set`,看三个数

  1. 数据规模数组越长,includes 每次扫描的成本越高
  2. 查询次数一次建好的 Set 被查的次数越多,越划算
  3. 更新频率源数据经常变,Set 就要跟着维护或重建

规范只要求 Set 的平均访问时间低于线性,没有规定必须用哈希表。常说的 O(1) 是主流引擎的实际表现,不是规范承诺。

核心回答

先记住这个答案

数组的 includes 从第一个元素开始逐个比较,找到才停,没找到要比完整个数组,所以是 O(n)。Set.has 在主流引擎里用哈希表实现,平均接近 O(1),不随数据量变慢。所以在 m 次查询、n 个元素的场景里,全用 includes 是 O(m × n),先建 Set 再查是 O(n + m)。但建 Set 本身要遍历一遍数组,还要多占内存,只查一两次、或者每次查询都重新建 Set,就得不偿失了。

  • 查数组是 O(n),查 Set 接近 O(1)
  • 建 Set 本身是 O(n),要被多次查询复用才划算
  • 别在循环里每次都 new Set
  • 小数组、只查一次,直接 includes 就好

一次建好,多次查询

典型场景:有一个允许的 ID 列表,要从一批数据里过滤出在列表中的项。

两种写法结果一样JavaScript
const allowed = ['a', 'c', 'e']
const queries = ['e', 'x', 'a', 'x']

// 每次都扫描 allowed 数组
const scanned = queries.filter(value => allowed.includes(value))

// 先建一次 Set,之后每次查询都是哈希查找
const index = new Set(allowed)
const indexed = queries.filter(value => index.has(value))

console.log(JSON.stringify(scanned))
console.log(JSON.stringify(indexed))
查看输出与解释
["e","a"]
["e","a"]

两种写法结果完全一样,区别在成本。scanned 那种写法,queries 里每一项都要把 allowed 扫一遍,两边都是几万条数据时就是几亿次比较。indexed 先花一次遍历建好 index,之后每次查询都很快。 new Set(allowed) 一定要放在 filter 外面。放进回调里,每次查询都会重建一次,比直接 includes 还慢。

不是所有场景都该换成 Set

数组很小,比如只有十几个元素时,includes 的线性扫描本身就很快,而建 Set 要分配哈希表,常数开销反而占了大头,差别基本感觉不到。只查一次的时候更是没必要,建 Set 已经遍历了一遍,不如直接 includes 遍历一遍。

数据会变时要考虑同步
如果 allowed 会经常增删,就要决定是每次变化后重建 Set,还是同步调用 add 和 delete 维护它。两份数据如果不同步,查询结果就会出错,这比性能差一点严重得多。能直接用 Set 作为唯一的数据来源最好,需要数组时再 [...set] 转出来。 真要比较性能,要用接近真实的数据规模和查询分布去测,并且把建 Set 的时间也算进去。只测 has 那一步,结论会偏向 Set。
回答前,多想一步

容易答错的地方

说规范保证 Set.has 每次都是 O(1)
规范只要求平均访问时间低于线性,没有规定必须是哈希表,也没有保证最坏情况。可以说「主流引擎里平均接近 O(1)」,不要说成规范承诺。
只比较查询,忽略建 Set 的成本
new Set(arr) 要遍历整个数组并建哈希表。每次查询前都要重新建的话,总成本就是建 Set 的成本,查询再快也没用。
试着用自己的话回答

面试官还会怎么问?

求两个数组的交集怎么写更快?

把其中一个转成 Set,再过滤另一个:const set = new Set(b); a.filter(x => set.has(x)),总共 O(m + n)。直接 a.filter(x => b.includes(x)) 是 O(m × n)。

Set.has 和 includes 对 NaN 的判断一样吗?

一样,两者都用 SameValueZero,都能找到 NaN,另外 +0 和 -0 也都算相等,对象都按引用比较。换写法时不用担心相等规则变了。

用对象的键做查找和 Set 比呢?

键都是字符串时,obj[key] !== undefined 或 key in obj 也很快,但要处理原型链上的属性冲突。Set 不仅没有这个问题,还能存任意类型的值,语义也更清楚,一般优先用 Set。

从一道题,走向一组知识

把知识连起来

数组与集合

Map.size 和 Object.keys(obj).length 数的到底是什么?

Object.keys 每次调用都要生成数组,和每次重建 Set 一样,都是高频操作里容易忽略的开销。

数组与集合

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

换成 Set 之前要确认相等规则一致,Set.has 和 includes 都用 SameValueZero,对象都按引用比较。

参考资料

  • MDN:Set 描述与性能边界

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

本题目录
  1. 先记住这个答案
  2. 一次建好,多次查询
  3. 不是所有场景都该换成 Set
  4. 容易答错的地方
  5. 面试官还会怎么问
  6. 把知识连起来
读懂,再试着讲出来

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

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