60 秒回答模板

数组去重最常用的是 Array.from(new Set(arr)) 或 [...new Set(arr)],它用 SameValueZero 判断相等,能处理 NaN,平均时间接近 O(n),适合基本类型。filter + indexOf 写法直观但每次都要查找,时间复杂度 O(n²),并且 indexOf 找不到 NaN。Map 或对象记录适合自定义 key,尤其是对象数组去重,例如按 id、name+type 等业务唯一键保留第一条或最后一条。回答时要说明:对象默认按引用比较,不会因为内容相同就相等;去重策略要先定义保留规则和 key 规则。

考点 核心机制与工程取舍
难度 中高频面试题
回答目标 按定义、机制、场景讲清楚

深入解析

01

先明确相等规则

基本类型可以按值去重,但对象、数组、函数默认按引用去重。两个内容相同的对象字面量不是同一个引用,Set 不会自动把它们合并。

02

Set 是首选基础写法

Array.from(new Set(arr)) 简洁,平均复杂度接近 O(n),并且 SameValueZero 会把 NaN 视为相等,也会把 +0 和 -0 视为相等。缺点是不能表达对象内容相等和保留策略。

03

filter + indexOf 适合说明原理

arr.filter((item, index) => arr.indexOf(item) === index) 表示只保留第一次出现的位置。但 indexOf 每次线性查找,整体 O(n²),且 indexOf(NaN) 为 -1,会把 NaN 边界答错。

04

Map 适合业务去重

对象数组应先定义 keyFn,例如 item.id 或 `${type}:${id}`。Map 可以保留插入顺序,先判断 has 再 set 表示保留第一条,直接 set 表示保留最后一条。

05

对象记录要注意 key 转换

普通对象会把 key 转成字符串,容易出现 1 和 '1' 混淆、__proto__ 等特殊键问题。若必须用对象记录,至少用 Object.create(null),但现代 JS 更推荐 Map。

javascript

数组去重的常用最小写法

const uniqueBySet = (arr) => Array.from(new Set(arr));

const uniqueByIndexOf = (arr) =>
  arr.filter((item, index) => arr.indexOf(item) === index);

function uniqueByKey(arr, keyFn) {
  const seen = new Map();
  for (const item of arr) {
    const key = keyFn(item);
    if (!seen.has(key)) seen.set(key, item);
  }
  return Array.from(seen.values());
}

uniqueByKey(users, (user) => user.id);
  • Set/Map 更适合 O(n) 去重;filter+indexOf 主要用于说明原理和简单小数组。
  • 对象数组必须显式提供 keyFn,否则只能按引用去重。

易错点

  • 说 Set 可以按对象内容去重,忽略对象默认按引用比较。
  • 把 filter + indexOf 当成 O(n),没有看到每个元素都要再查一遍数组。
  • 不知道 indexOf(NaN) 为 -1,导致 NaN 去重结果错误。
  • 对象数组不定义业务 key,直接 JSON.stringify 去重,遇到字段顺序、循环引用和非 JSON 值就不可靠。

面试官追问

Set 能去重对象数组吗?

只能去掉同一个对象引用的重复项。两个内容相同但引用不同的对象不会被合并;要按 id 等业务 key 用 Map 去重。

filter + indexOf 有什么坑?

它是 O(n²),大数组性能差;indexOf 找不到 NaN,所以包含 NaN 时结果不符合常见预期。

保留第一条和保留最后一条怎么实现?

Map 里先判断 has 再 set 是保留第一条;不判断直接 set 会让后出现的同 key 元素覆盖前者,结果保留最后一条。

为什么不推荐普通对象做通用去重表?

普通对象 key 会字符串化,可能混淆数字和字符串,还会碰到原型链特殊字段。Map 能保留原始 key 类型,语义更清楚。