JavaScript 数组去重的六种方法实现与性能对比
数组去重是前端开发中的经典面试题与高频需求。本文总结了六种 JavaScript 数组去重的实现思路,涵盖从基础遍历到现代 ES6 语法的多种方案。通过对每种方法的代码解析与性能对比,揭示了时间换空间与空间换时间的权衡关系,助力开发者在实际项目中灵活应用。
结果与目标
在使用 JavaScript 处理数据时,去除 Array 中的重复项是一个常见且基础的需求。虽然简单,但不同的实现方式在运行效率(时间复杂度)和内存占用(空间复杂度)上存在显著差异。理解这些差异对于编写高性能代码至关重要。
准备条件
所有方法均针对 JavaScript 原生数组进行操作,无需引入第三方库。代码片段可直接在浏览器控制台或 Node.js 环境中运行测试。
真实操作顺序与方法详解
第 1 步:使用 indexOf 进行线性查找
目的:利用原生的 indexOf 方法判断元素是否已存在于临时数组中。
Array.prototype.unique1 = function() {
var n = []; // 新的临时数组
for (var i = 0; i < this.length; i++) {
// 如果当前项在临时数组中不存在(indexOf返回-1),则添加
if (n.indexOf(this[i]) == -1) n.push(this[i]);
}
return n;
}
此方法直观易懂,但性能一般。因为每次 push 前都要在 n 数组中遍历查找,随着数组增长,查找耗时增加。
第 2 步:利用 Hash 表(对象)优化查找
目的:通过对象的键值对特性将查找时间复杂度降至 O(1),实现“空间换时间”。
Array.prototype.unique2 = function() {
var n = {}, r = []; // n为hash表,r为结果数组
for (var i = 0; i < this.length; i++) {
if (!n[this[i]]) { // 若hash表中没有该项
n[this[i]] = true; // 标记为已存在
r.push(this[i]); // 加入结果数组
}
}
return r;
}
这是传统方法中效率较高的一种。它避免了数组的线性搜索,利用对象属性的快速索引特性。需要注意的是,这种方法主要适用于字符串或数字类型的去重,若包含对象或复杂结构,Key 可能会被强制转换为字符串导致逻辑错误。
第 3 步:结合 indexOf 检查首次出现位置
目的:验证当前元素在当前数组中的第一次出现索引是否与当前循环索引一致。
Array.prototype.unique3 = function() {
var n = [this[0]]; // 结果数组
for (var i = 1; i < this.length; i++) {
// 如果当前项在原数组中第一次出现的位置不是i,说明是重复项
if (this.indexOf(this[i]) == i) n.push(this[i]);
}
return n;
}
逻辑与第 1 种类似,同样依赖于 indexOf 的内部遍历,因此在大数据量下性能较差。
第 4 步:先排序再比较相邻元素
目的:通过排序使相同元素相邻,从而只需一次遍历即可去重。
Array.prototype.unique4 = function() {
this.sort(); // 原生sort,通常为快速排序
var re = [this[0]];
for (var i = 1; i < this.length; i++) {
// 仅当当前项与前一项不同时才加入结果集
if (this[i] !== re[re.length - 1]) {
re.push(this[i]);
}
}
return re;
}
该方法利用了 sort()indexOf 的方案,通常快于第一种和第三种方法。
第 5 步:兼容旧版本的 indexOf 封装
目的:解决 IE8 及以下版本不支持原生 Array.prototype.indexOf 的问题。
Array.prototype.unique = function() {
var length = this.length;
if (length <= 1) return this;
// 兼容性处理:若环境不支持indexOf,则手动实现
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function(item) {
var l = this.length, i = 0, r = -1;
if (l <= 0) return -1;
for (; i < l; i++) {
if (this[i] === item) r = i;
}
return r;
}
}
var result = [];
for (var i = 0; i < length; i++) {
if (result.indexOf(this[i]) === -1) {
result.push(this[i]);
}
}
return result;
}
此方法主要用于兼容遗留系统,核心逻辑同第 1 种,适合对浏览器兼容性有严格要求的老项目维护。
第 6 步:双重循环暴力匹配
目的:最基础的逐一对比方法,理解算法底层逻辑。
function unique(arr) {
var result = [], isRepeated;
for (var i = 0, len = arr.length; i < len; i++) {
isRepeated = false;
for (var j = 0, lenR = result.length; j < lenR; j++) {
if (arr[i] == result[j]) {
isRepeated = true;
break;
}
}
if (!isRepeated) {
result.push(arr[i]);
}
}
return result;
}
该方案通过嵌套循环检查每个元素是否在结果数组中存在。由于涉及两层循环,时间复杂度为 O(n²),效率极低,仅适用于极小规模数据的演示。
补充方法:ES6 Set(现代推荐方案)
虽然原文未列出,但在现代 JavaScript 开发中,使用 Set 是最简洁且高效的方式:const uniqueArr = [...new Set(array)];
其底层基于哈希表实现,兼具高速度与简洁语法,强烈建议在新项目中优先使用。
完成判断
- 效率最佳:方法 2(Hash 表)和方法 6(若优化为 Hash 查找),以及现代的 Set 方案。
- 空间权衡:Hash 表方法(方法 2)因创建了额外的对象容器,内存占用略高,但换取了执行速度的提升。
- 兼容性:方法 5 专门解决了老旧环境的兼容问题。
- 可读性:方法 4(排序法)代码简短,逻辑清晰,适合对排序无副作用的场景。
简要总结
数组去重的选择需权衡时间、空间和兼容性。传统面试常考的原理是 Hash 表与 indexOf 的区别;而在实际工程中,推荐使用 Set 或优化的 Hash 映射策略以避免性能瓶颈。以上就是 JavaScript 数组去重的详细内容,更多关于 JavaScript 编程技巧的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













