当前位置:首页 > 文章列表 > 文章 > 前端 > JS去除数组重复项的5种方法

JS去除数组重复项的5种方法

2025-08-07 09:21:53 0浏览 收藏

从现在开始,我们要努力学习啦!今天我给大家带来《JS删除数组重复项的几种方法》,感兴趣的朋友请继续看下去吧!下文中的内容我们主要会涉及到等等知识点,如果在阅读本文过程中有遇到不清楚的地方,欢迎留言呀!我们一起讨论,一起学习!

最直接、最现代的JavaScript数组去重方法是使用Set,因其设计初衷即为存储唯一值,可高效去除基本类型重复项;2. 对于对象数组去重,需基于唯一标识属性结合Map实现,或通过自定义比较逻辑处理复杂场景;3. 需警惕类型隐式转换、NaN特殊性等潜在陷阱,并根据数据规模权衡性能与可读性,确保明确“重复”定义后再选择合适方案。

js怎么删除数组中的重复项

要说JavaScript里怎么去掉数组重复项,最直接、最现代的答案就是用Set。它就像个天然的去重机,一丢进去,出来就都是独一无二的了。

js怎么删除数组中的重复项

解决方案

在我看来,处理数组去重,最优雅且效率不错的方案,首推ES6的Set对象。它的设计初衷就是为了存储不重复的值,所以用它来去重简直是量身定制。

使用 Set 对象

js怎么删除数组中的重复项

这种方法简洁明了,可读性极高。

function uniqueArrayWithSet(arr) {
  return [...new Set(arr)];
  // 或者:return Array.from(new Set(arr));
}

const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = uniqueArrayWithSet(numbers);
console.log(uniqueNumbers); // 输出: [1, 2, 3, 4, 5]

const strings = ['apple', 'banana', 'apple', 'orange'];
const uniqueStrings = uniqueArrayWithSet(strings);
console.log(uniqueStrings); // 输出: ['apple', 'banana', 'orange']

这种方式的优点是代码量少,语义清晰,而且内部实现通常是经过优化的,对于大多数情况性能表现都很好。它能处理基本数据类型(数字、字符串、布尔值、nullundefined)的去重。不过,对于对象(Object),Set是基于引用的去重,这意味着 {a:1}{a:1} 会被视为两个不同的值,因为它俩在内存中的引用地址不同。这一点在使用时需要特别注意。

js怎么删除数组中的重复项

当然,除了 Set 这种“现代”方式,JavaScript 发展这么多年,也积累了不少“传统”的去重手段,它们在特定场景下依然有用,或者说,理解它们能帮助我们更好地理解数组操作的底层逻辑。

为什么数组去重是个常见需求?(以及我们为什么要关心效率)

说实话,我在日常开发中,遇到数组去重的场景简直太多了。这并不是一个孤立的问题,它几乎渗透在数据处理的方方面面。比如,你可能从后端API拿了一堆数据,结果发现某些ID或者标签重复了,你肯定不希望在前端展示的时候也重复吧?又或者,用户在一个多选框里不小心点了两次同一个选项,你后台接收到的数组里就有了重复项。再比如,当你合并多个数组时,为了保持数据的纯粹性,去重就成了必不可少的一步。

为什么要关心效率?这其实是个很实际的问题。如果你的数组只有几十个、几百个元素,那么用什么方法去重,性能差异几乎可以忽略不计。但如果你的数组有几万、几十万甚至上百万个元素,那么一个O(N²)复杂度的算法,和O(N)或者O(N log N)的算法,其执行时间可能就是几毫秒和几秒甚至几十秒的区别。在用户体验至上的今天,没有人愿意等待一个慢吞吞的页面。所以,选择一个合适的、高效的去重方法,不仅仅是代码写得好不好看的问题,更是直接影响产品性能和用户体验的关键。这就像你在修一条路,小路可能随便铺铺就行,但要是修高速公路,你肯定要考虑材料、施工方式和未来的承载能力,对吧?

除了Set,还有哪些经典的JavaScript数组去重方法?(性能与场景考量)

除了 Set 这个“万金油”,我们还有一些其他经典的去重方法,它们各有特点,也适合不同的场景。理解它们能帮助你更灵活地应对各种去重需求。

1. 使用 filter() 结合 indexOf()

这是非常经典的一种方式,也相对容易理解。filter() 方法会创建一个新数组,其中包含通过所提供函数实现的测试的所有元素。而 indexOf() 则会返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回 -1。

function uniqueArrayWithFilterAndIndexOf(arr) {
  return arr.filter((item, index, self) => {
    return self.indexOf(item) === index;
  });
}

const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = uniqueArrayWithFilterAndIndexOf(numbers);
console.log(uniqueNumbers); // 输出: [1, 2, 3, 4, 5]

性能考量: 这种方法对于每个元素,都需要遍历(或至少部分遍历)数组来查找其首次出现的位置。因此,它的时间复杂度是 O(N²)。对于小型数组(几百个元素以内),这种性能开销通常可以接受,代码也比较直观。但如果数组非常大,性能瓶颈就会非常明显,我个人是不太推荐在大规模数据处理中使用它。

2. 使用 reduce() 结合 includes()Map/Object

reduce() 方法可以把数组“规约”成一个单一的值,这里我们可以用它来构建一个不重复的新数组。

// 方法A: 结合 includes()
function uniqueArrayWithReduceAndIncludes(arr) {
  return arr.reduce((accumulator, current) => {
    if (!accumulator.includes(current)) {
      accumulator.push(current);
    }
    return accumulator;
  }, []);
}

const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = uniqueArrayWithReduceAndIncludes(numbers);
console.log(uniqueNumbers); // 输出: [1, 2, 3, 4, 5]

性能考量: 类似 filter + indexOfincludes() 也会在每次迭代时遍历 accumulator 数组,所以这种方法的平均时间复杂度也是 O(N²)。

方法B: 结合 Map 或普通 Object (作为哈希表)

这种方式的思路是利用 Map 或普通对象的键值对特性,将数组中的元素作为键存储,从而达到去重的目的。因为 Map 或对象查找键的效率非常高(接近 O(1)),所以整体性能会好很多。

function uniqueArrayWithReduceAndMap(arr) {
  const map = new Map();
  return arr.reduce((accumulator, current) => {
    if (!map.has(current)) {
      map.set(current, true); // 存入Map,标记已见过
      accumulator.push(current);
    }
    return accumulator;
  }, []);
}

const numbers = [1, 2, 2, 3, 4, 4, 5];
const uniqueNumbers = uniqueArrayWithReduceAndMap(numbers);
console.log(uniqueNumbers); // 输出: [1, 2, 3, 4, 5]

// 也可以直接用Object,但Map在键类型和性能上更灵活
function uniqueArrayWithObject(arr) {
  const obj = {};
  const result = [];
  for (let i = 0; i < arr.length; i++) {
    const item = arr[i];
    if (!obj[item]) { // 判断是否已存在
      obj[item] = true;
      result.push(item);
    }
  }
  return result;
}

const moreNumbers = [1, 2, 2, 3, 4, 4, 5, '1', 'a', 'a']; // 注意 '1' 和 1 的区别
const uniqueMoreNumbers = uniqueArrayWithObject(moreNumbers);
console.log(uniqueMoreNumbers); // 输出: [1, 2, 3, 4, 5, "1", "a"]

性能考量: 这种方法的时间复杂度接近 O(N),因为 Map.has() 或对象属性查找的平均时间是常数时间。这使得它在处理大型数组时,性能表现非常接近 Set,是除了 Set 之外我个人比较推荐的通用去重方案。

总结一下:

  • Set: 最佳选择,代码简洁,性能优异(O(N)),但要注意对象去重是基于引用的。
  • filter + indexOf / reduce + includes: 代码直观,但性能较差(O(N²)),不适合大型数组。
  • reduce + Map/Object: 性能优异(O(N)),可以作为 Set 的替代,尤其是在需要兼容旧环境,或者对键有特殊处理需求时。

选择哪种方法,说到底还是要看你的具体需求:数组大小、数据类型、以及对代码简洁性或兼容性的偏好。

处理包含对象的数组去重:一个更复杂的挑战

前面我们讨论的去重方法,对于基本数据类型(数字、字符串等)都很有效。但当数组里装的是对象时,事情就变得有点复杂了。这是因为 JavaScript 在比较对象时,默认是比较它们的引用地址,而不是它们内部的属性值。也就是说,{ id: 1, name: 'A' }{ id: 1, name: 'A' } 在内存中是两个不同的对象,即使它们的属性值完全一样,Set 也会认为它们是不同的。这就像你有两张一模一样的照片,但它们是不同的冲印出来的纸张,不是同一张。

那么,如果我们想根据对象的某个或某几个属性来判断“重复”,该怎么办呢?

1. 根据唯一标识属性去重 (推荐)

如果你的对象有一个或多个可以作为唯一标识的属性(比如 iduuidsku 等),这是最常用也最可靠的方法。我们可以利用 Map 来存储这些唯一标识,并把对应的对象存起来。

function uniqueObjectsById(arr, key) {
  const map = new Map();
  const result = [];
  for (const item of arr) {
    // 确保对象有这个key,并且这个key的值不为空
    if (item && item[key] !== undefined && !map.has(item[key])) {
      map.set(item[key], item); // 以key的值作为Map的键
      result.push(item);
    }
  }
  return result;
}

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 1, name: 'Alicia' }, // id重复,但name不同
  { id: 3, name: 'Charlie' },
  { id: 2, name: 'Bobby' } // id重复,但name不同
];

const uniqueUsers = uniqueObjectsById(users, 'id');
console.log(uniqueUsers);
/*
输出:
[
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 3, name: 'Charlie' }
]
*/

这种方法非常高效,因为它利用了 Map 的 O(1) 查找特性。它会保留第一个遇到的具有该 id 的对象。

2. 将对象转换为字符串进行比较 (有风险,慎用)

如果你没有一个明确的唯一标识符,或者需要根据对象的所有属性来判断重复,一个“歪招”是把对象转换成字符串(比如用 JSON.stringify),然后对这些字符串进行去重。

function uniqueObjectsByStringify(arr) {
  const stringifiedSet = new Set();
  const result = [];
  for (const item of arr) {
    const stringifiedItem = JSON.stringify(item);
    if (!stringifiedSet.has(stringifiedItem)) {
      stringifiedSet.add(stringifiedItem);
      result.push(item);
    }
  }
  return result;
}

const data = [
  { a: 1, b: 2 },
  { b: 2, a: 1 }, // 属性顺序不同,JSON.stringify结果可能不同!
  { a: 1, b: 2 },
  { c: 3, d: 4 }
];

const uniqueData = uniqueObjectsByStringify(data);
console.log(uniqueData);
/*
输出:
[
  { a: 1, b: 2 },
  { b: 2, a: 1 }, // 注意这里,如果属性顺序不同,会被认为是两个不同的对象
  { c: 3, d: 4 }
]
*/

风险点:

  • 属性顺序: JSON.stringify 的结果会受对象属性顺序的影响。{a:1, b:2}{b:2, a:1} 转换成的字符串是不同的,即使它们在逻辑上是“相同”的对象。
  • 循环引用: 如果对象中存在循环引用,JSON.stringify 会抛出错误。
  • 不可序列化的值: 如果对象包含 undefined、函数、Symbol 值,或者 BigIntJSON.stringify 会忽略它们或抛出错误。

所以,这种方法只适用于非常简单的、属性顺序固定的、不含特殊值的对象数组。在我看来,除非你对数据结构有绝对的掌控,否则尽量避免这种方式。

3. 自定义比较函数 (更灵活但复杂)

对于更复杂的场景,比如需要根据多个属性组合判断唯一性,或者属性值本身也是对象,你可能需要编写一个自定义的比较函数,然后结合 filterreduce 来实现。

function uniqueObjectsByCustomLogic(arr, compareFn) {
  const result = [];
  arr.forEach(item => {
    // 检查结果数组中是否已存在与当前item“相同”的元素
    const isDuplicate = result.some(existingItem => compareFn(existingItem, item));
    if (!isDuplicate) {
      result.push(item);
    }
  });
  return result;
}

// 示例比较函数:根据 id 和 type 两个属性判断
const compareUsers = (user1, user2) => user1.id === user2.id && user1.type === user2.type;

const complexUsers = [
  { id: 1, type: 'admin', name: 'A' },
  { id: 2, type: 'user', name: 'B' },
  { id: 1, type: 'admin', name: 'C' }, // id和type都重复
  { id: 1, type: 'guest', name: 'D' }  // id重复,但type不同
];

const uniqueComplexUsers = uniqueObjectsByCustomLogic(complexUsers, compareUsers);
console.log(uniqueComplexUsers);
/*
输出:
[
  { id: 1, type: 'admin', name: 'A' },
  { id: 2, type: 'user', name: 'B' },
  { id: 1, type: 'guest', name: 'D' }
]
*/

性能考量: 这种方法因为 some() 内部的循环,以及 compareFn 的执行,性能通常是 O(N²)。对于大型数组,需要慎重考虑。但它的优势在于极高的灵活性,你可以根据任何复杂的逻辑来定义“重复”。

总的来说,处理对象数组去重,核心思想就是找到一个可靠的“判重”依据。如果能通过某个唯一ID,那用 Map 绝对是首选。如果不行,就得根据具体业务逻辑来权衡是接受 JSON.stringify 的局限性,还是投入精力去写一个更复杂的自定义比较函数。

数组去重时可能遇到的“坑”和最佳实践

在处理数组去重时,虽然方法很多,但有些小细节或者“坑”是需要注意的。这些东西往往不是代码本身的问题,而是数据特性或者我们对数据理解的偏差导致的。

1. 数据类型隐式转换的陷阱

JavaScript 的类型转换有时会让人头疼。比如,当你用 indexOf 或者 Set 去重时,1'1' 是被认为是不同的值。但如果你用 Object 作为哈希表,并且键是数字,那么 obj[1]obj['1'] 可能会指向同一个属性,因为对象键会被强制转换为字符串。

const mixedArray = [1, '1', 2, '2', 1];
const uniqueWithSet = [...new Set(mixedArray)];
console.log(uniqueWithSet); // [1, "1", 2, "2"] - Set区分了数字和字符串

const uniqueWithObjectHash = (() => {
  const obj = {};
  const result = [];
  for (const item of mixedArray) {
    // obj[item] 会将 item 转换为字符串作为键
    // obj[1] 和 obj['1'] 都会变成 obj['1']
    if (!obj[item]) {
      obj[item] = true;
      result.push(item);
    }
  }
  return result;
})();
console.log(uniqueWithObjectHash); // [1, 2] - '1' 和 '2' 被视为重复

这里 uniqueWithObjectHash 的结果可能会出乎意料,因为它将 1'1' 视为相同。所以,在去重前,最好确保你的数组元素类型是一致的,或者你清楚这种类型转换带来的影响。

2. NaN 的特殊性

NaN(Not a Number)是一个非常特殊的值。在 JavaScript 中,NaN 和任何值都不相等,包括它自己 (NaN === NaN 返回 false)。这意味着,如果你数组里有多个 NaNindexOf 会认为它们都是不同的。

const nanArray = [1, NaN, 2, NaN, 3];
const uniqueWithFilter = nanArray.filter((item, index, self) => self.indexOf(item) === index);
console.log(uniqueWithFilter); // [1, NaN, 2, NaN, 3] - 两个NaN都保留了

const uniqueWithSet = [...new Set(nanArray)];
console.log(uniqueWithSet); // [1, NaN, 2, 3] - Set 对 NaN 的处理是特殊的,它只会保留一个 NaN

Set 在处理 NaN 时表现得更“智能”,它只会存储一个 NaN。这是 Set 的一个优点,但如果你不了解,可能会感到困惑。

3. 性能与可读性的权衡

前面提到了各种方法的性能差异。在实际开发中,我们总是在性能和代码可读性之间做权衡。对于小数组,我个人倾向于选择最直观、最简洁的 Set 方法,或者 filter + indexOf,因为它们的性能开销几乎可以忽略,而代码维护成本低。只有当面对性能瓶颈时,才会去考虑更复杂的优化,比如手动构建哈希表。过度优化一个非瓶颈点,反而会增加代码的复杂性。

4. 保持数组的原始顺序

大部分去重方法都会保留元素在原数组中的首次出现顺序,比如 Setfilterreduce。但如果你自己实现一些基于排序的去重(比如先排序,再遍历去重),那么原始顺序就会丢失。这在某些业务场景下可能是不可接受的。

5. 明确“重复”的定义

尤其是在处理对象数组时,最关键的一点就是:你到底如何定义“重复”?是所有属性都相同才算重复?还是某个ID相同就算重复?这个定义直接决定了你选择哪种去重策略,以及如何编写你的比较逻辑。没有明确的定义,再好的去重方法也可能无法满足你的需求。

在我看来,最好的实践是:

  • 优先使用 Set

今天关于《JS去除数组重复项的5种方法》的内容介绍就到此结束,如果有什么疑问或者建议,可以在golang学习网公众号下多多回复交流;文中若有不正之处,也希望回复留言以告知!

jQuery跳跃动画实现与animate()常见问题解析jQuery跳跃动画实现与animate()常见问题解析
上一篇
jQuery跳跃动画实现与animate()常见问题解析
Python代码审计:AST遍历技巧解析
下一篇
Python代码审计:AST遍历技巧解析
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    542次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    511次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    498次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    484次学习
查看更多
AI推荐
  • 千音漫语:智能声音创作助手,AI配音、音视频翻译一站搞定!
    千音漫语
    千音漫语,北京熠声科技倾力打造的智能声音创作助手,提供AI配音、音视频翻译、语音识别、声音克隆等强大功能,助力有声书制作、视频创作、教育培训等领域,官网:https://qianyin123.com
    119次使用
  • MiniWork:智能高效AI工具平台,一站式工作学习效率解决方案
    MiniWork
    MiniWork是一款智能高效的AI工具平台,专为提升工作与学习效率而设计。整合文本处理、图像生成、营销策划及运营管理等多元AI工具,提供精准智能解决方案,让复杂工作简单高效。
    118次使用
  • NoCode (nocode.cn):零代码构建应用、网站、管理系统,降低开发门槛
    NoCode
    NoCode (nocode.cn)是领先的无代码开发平台,通过拖放、AI对话等简单操作,助您快速创建各类应用、网站与管理系统。无需编程知识,轻松实现个人生活、商业经营、企业管理多场景需求,大幅降低开发门槛,高效低成本。
    131次使用
  • 达医智影:阿里巴巴达摩院医疗AI影像早筛平台,CT一扫多筛癌症急慢病
    达医智影
    达医智影,阿里巴巴达摩院医疗AI创新力作。全球率先利用平扫CT实现“一扫多筛”,仅一次CT扫描即可高效识别多种癌症、急症及慢病,为疾病早期发现提供智能、精准的AI影像早筛解决方案。
    126次使用
  • 智慧芽Eureka:更懂技术创新的AI Agent平台,助力研发效率飞跃
    智慧芽Eureka
    智慧芽Eureka,专为技术创新打造的AI Agent平台。深度理解专利、研发、生物医药、材料、科创等复杂场景,通过专家级AI Agent精准执行任务,智能化工作流解放70%生产力,让您专注核心创新。
    128次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议隐私政策
返回登录
  • 重置密码