JavaScript数组去重:5种主流方案性能实测与对比

作者:极速慢跑时间:2026-10-29 23:46:45浏览:153

本文深入解析JavaScript中数组去重的多种实现方式,包括双重循环、排序遍历、对象键值对、indexOf及ES6 Set。通过Chrome控制台实测各方法耗时,对比11031ms至621ms的性能差异,并指出类型混淆等潜在问题,帮助开发者根据场景选择最优方案。

推荐阅读:JavaScript学习笔记之数组的增、删、改、查

JavaScript学习笔记之数组求和方法

JavaScript学习笔记之数组随机排序

JavaScript数组去重方法概述

在面试中,面试官常会考察JavaScript实现数组去重的能力。近期笔者在系统学习JavaScript数组相关知识时,趁此机会整理了几种常见的数组去重实现方法。

以下整理的方法均经过收集与验证,若文中存在疏漏或错误,欢迎指正。

双重循环去重实现与性能分析

双重循环去重

该方案的核心逻辑是利用两层for循环进行遍历,具体思路如下:

首先初始化一个空数组,用于存储去重后的结果。

外层循环遍历原数组,每次取出一个元素与结果数组中的元素进行比对。若发现原数组元素与结果数组中某元素相同,则终止当前内层循环;若遍历完结果数组均未找到相同元素,则将该元素存入结果数组。

具体代码实现如下:


Array.prototype.unique1 = function () {
// 构建一个新数组,存放结果
var newArray = [this[0]];
// for循环,每次从原数组中取出一个元素
// 用取出的元素循环与结果数组对比
for (var i = 1; i < this.length; i++) {
var repeat = false;
for (var j=0; j < newArray.length; j++) {
// 原数组取出的元素与结果数组元素相同
if(this[i] == newArray[j]) {
repeat = true;
break;
}
}
if(!repeat) {
// 如果结果数组中没有该元素,则存放到结果数组中
newArray.push(this[i]);
}
}
return newArray;
}

假设存在如下测试数组:


var arr = [1,2,3,4,'a','b',1,3,4,56,32,34,2,'b','c',5,'1',`2`];
arr.unique1(); // [1, 2, 3, 4, "a", "b", 56, 32, 34, "c", 5]

业界普遍认为该方法耗时较长且性能较差。为验证此观点,笔者编写了简单的测试脚本(测试逻辑较为简陋):


function test () {
var arr = [];
for (var i = 0; i < 1000000; i++) {
arr.push(Math.round(Math.random(i) * 10000));
}
doTest(arr, 1);
}
function doTest(arr, n) {
var tStart = (new Date()).getTime();
var re = arr.unique1();
var tEnd = (new Date()).getTime();
console.log('双重循环去重方法使用时间是:' + (tEnd - tStart) + 'ms');
return re;
}
test();

在Chrome开发者工具中运行上述代码,双重循环去重方法的耗时约为11031ms。

上述逻辑可以通过forEach()结合indexOf()方法进行模拟实现:


function unique1() {
var newArray = [];
this.forEach(function (index) {
if (newArray.indexOf(index) == -1) {
newArray.push(index);
}
});
return newArray;
}

通过unique1.apply(arr)或unique1.call(arr)的方式调用。经测试,该方法的效率显著提升,相同测试代码下的耗时降至5423ms,速度几乎提升了一倍。

排序遍历去重及其局限性

排序遍历去重

首先调用sort()方法对原数组进行排序,随后遍历排序后的数组,检查当前第i个元素与结果数组的最后一个元素是否相同。若不同,则将该元素追加至结果数组中。


Array.prototype.unique2 = function () {
// 原数组先排序
this.sort();
// 构建一个新数组存放结果
var newArray = [];
for (var i = 1; i < this.length; i++) {
// 检查原数中的第i个元素与结果中的最后一个元素是否相同
// 因为排序了,所以重复元素会在相邻位置
if(this[i] !== newArray[newArray.length - 1]) {
// 如果不同,将元素放到结果数组中
newArray.push(this[i]);
}
}
return newArray;
}

示例如下:


var arr = [1,2,3,4,'a','b',1,3,4,56,32,34,2,'b','c',5,'1','2'];
arr.unique2(); // ["1", 1, 2, "2", 3, 32, 34, 4, 5, 56, "a", "b", "c"]

该方法具有两个显著特点:

由于去重前执行了排序操作,最终返回的数组也是有序状态。

该方法无法区分数值类型与字符串类型的相同值,例如数字1与字符串'1'会被视为不同元素保留。

使用相同测试方法测得该方案耗时为1232ms。

对象键值对法:高效但占用内存

对象键值对法

该去重方案的核心思路如下:

创建一个JavaScript对象和一个新数组。

使用for循环遍历原数组,每次取出一个元素,并将其与JavaScript对象的键名进行比对。

若对象中不包含该键名,则将该元素推入结果数组,并在对象中设置该属性名为1。

代码实现如下:


Array.prototype.unique3 = function () {
// 构建一个新数组存放结果
var newArray = [];
// 创建一个空对象
var object = {};
// for循环时,每次取出一个元素与对象进行对比
// 如果这个元素不重复,则将它存放到结果数中
// 同时把这个元素的内容作为对象的一个属性,并赋值为1,
// 存入到第2步建立的对象中
for (var i = 0; i < this.length; i++){
// 检测在object对象中是否包含遍历到的元素的值
if(!object[typeof(this[i]) + this[i]]) {
// 如果不包含,将存入对象的元素的值推入到结果数组中
newArray.push(this[i]);
// 如果不包含,存入object对象中该属性名的值设置为1
object[typeof(this[i]) + this[i]] = 1;
}
}
return newArray;
}

运行前述示例代码:


var arr = [1,2,3,4,'a','b',1,3,4,56,32,34,2,'b','c',5,'1','2'];
arr.unique3(); // [1, 2, 3, 4, "a", "b", 56, 32, 34, "c", 5, "1", "2"]

同样存在键名混淆的问题,例如a[1]与a["1"]在对象中可能被视为同一键。该方法耗时仅为621ms,是所有方法中最快的,但缺点是占用内存相对较大。

其他去重方案及综合性能对比

除了上述几种方法外,还存在其他几种实现方式,具体如下:


// 方法四
Array.prototype.unique4 = function () {
// 构建一个新数组存放结果
var newArray = [];
// 遍历整个数组
for (var i = 0; i < this.length; i++) {
// 遍历是否有重复的值
for (j = i + 1; j < this.length; j++) {
// 如果有相同元素,自增i变量,跳出i的循环
if(this[i] === this[j]) {
j = ++i;
}
}
// 如果没有相同元素,将元素推入到结果数组中
newArray.push(this[i]);
}
return newArray;
}

Chrome浏览器测试结果


var arr = [1,2,3,4,'a','b',1,3,4,56,32,34,2,'b','c',5,'1','2'];
arr.unique4(); // ["a", 1, 3, 4, 56, 32, 34, 2, "b", "c", 5, "1", "2"]

同样存在1和'1'无法区分的问题。


// 方法五
Array.prototype.unique5 = function () {
// 构建一个新数组存放结果
var newArray = [];
// 遍历整个数组
for (var i = 0; i < this.length; i++) {
// 如果当前数组的第i值保存到临时数组,那么跳过
var index = this[i];
// 如果数组项不在结果数组中,将这个值推入结果数组中
if (newArray.indexOf(index) === -1) {
newArray.push(index);
}
}
return newArray;
}

Chrome浏览器测试结果:


var arr = [1,2,3,4,'a','b',1,3,4,56,32,34,2,'b','c',5,'1','2'];
arr.unique6(); // [1, 2, 3, 4, "a", "b", 56, 32, 34, "c", 5, "1", "2"]

同样存在1和'1'无法区分的情况。该方法耗时为14361ms。


// 方法六
Array.prototype.unique6 = function () {
return this.reduce(function (newArray, index) {
if(newArray.indexOf(index) < 0) {
newArray.push(index);
}
return newArray;
},[]);
}

测试结果如下:


var arr = [1,2,3,4,'a','b',1,3,4,56,32,34,2,'b','c',5,'1','2'];
arr.unique6(); // [1, 2, 3, 4, "a", "b", 56, 32, 34, "c", 5, "1", "2"]

该方法耗时为16490ms。


// 方法七
Array.prototype.unique7 = function(){
var newArray;
newArray = this.filter(function (ele,i,arr) {
return arr.indexOf(ele) === i;
});
return newArray;
}

测试结果:


var arr = [1,2,3,4,'a','b',1,3,4,56,32,34,2,'b','c',5,'1','2'];
arr.unique6(); // [1, 2, 3, 4, "a", "b", 56, 32, 34, "c", 5, "1", "2"]

该方法耗时为13201ms。

推荐方案与ES6优化方案

综合对比上述多种方法,以下方案在性能与兼容性之间取得了较好的平衡,被视为较为优秀的方案:


Array.prototype.unique3 = function () {
// 构建一个新数组存放结果
var newArray = [];
// 创建一个空对象
var object = {};
// for循环时,每次取出一个元素与对象进行对比
// 如果这个元素不重复,则将它存放到结果数中
// 同时把这个元素的内容作为对象的一个属性,并赋值为1,
// 存入到第2步建立的对象中
for (var i = 0; i < this.length; i++){
// 检测在object对象中是否包含遍历到的元素的值
if(!object[typeof(this[i]) + this[i]]) {
// 如果不包含,将存入对象的元素的值推入到结果数组中
newArray.push(this[i]);
// 如果不包含,存入object对象中该属性名的值设置为1
object[typeof(this[i]) + this[i]] = 1;
}
}
return newArray;
}

此外,在ES6标准中还有更简洁、更优化的去重方案,例如:


// ES6
function unique (arr) {
const seen = new Map()
return arr.filter((a) => !seen.has(a) && seen.set(a, 1))
}
// or
function unique (arr) {
return Array.from(new Set(arr))
}

以上即为笔者整理的JavaScript数组去重学习笔记,希望能对读者有所帮助。

作者最新文章