本文系统梳理JS数组去重的4大类型:元素比较、位置查找、存在性判断及数据结构利用。重点解析双层循环、indexOf、includes及ES6 Map/Set的实现原理与局限,特别是NaN等值的特殊处理,推荐Set为最常用方案。
1 测试用例
// 测试用例
const a = {};
const b = { c: 1 };
const array = [
1, 1, "1", "1",
{}, {}, { c: 1 }, { c: 1},
a, a, b, b,
[], [], [1], [1],
undefined, undefined,
null, null,
NaN, NaN,
];
2 JS 数组去重4大类型
2.1 元素比较型
此类方法通过直接对比数组中各个元素是否相等来实现去重。
2.1.1 双层 for 循环逐一比较(es5常用)
利用双层 for 循环遍历数组元素,并使用 splice 方法移除重复项。
// 双层for循环
function uniq1(arr) {
for (let i = 0; i < arr.length; i++) {
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] === arr[j]) {
arr.splice(j, 1)
j--
}
}
}
return arr
}
// 去重结果
// [1,'1',{},{},{c:1},{c:1},{},{c:1},[],[],[1],[1],undefined,null,NaN,NaN]
对比去重前后的结果发现,重复的 NaN 并未被去除,原因在于 NaN === NaN 的结果为 false。
2.1.2 排序相邻比较
首先使用 sort() 方法对数组进行排序,随后比较相邻元素,并通过 splice 方法剔除重复项。
function uni2(arr) {
arr.sort();
for (let i = 0; i < arr.length - 1; i++) {
arr[i] === arr[i + 1] && arr.splice(i + 1, 1) && i--;
}
return arr;
}
另一种做法是创建新数组,仅将不重复的元素推入新数组中。
function uniq3(arr) {
arr = arr.sort()
const newArr = [arr[0]]
for (let i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i - 1]) {
newArr.push(arr[i])
}
}
return newArr
}
// 去重结果
// [[],[],1,'1',[1],[1],NaN,NaN,{},{},{c:1},{c:1},{},{c:1},null,undefined]
同样地,重复的 NaN 未能去除,因为 NaN === NaN为false 默认排序会将元素转为字符串,而对象转字符串均为
sort[object Object],导致 sort 方法无法对对象数组正确排序,进而可能无法去重重复对象(除非它们原本相邻)。
2.2 查找元素位置型
此类方法通过查找元素首次出现的位置索引来实现去重。
2.2.1 indexOf
利用 indexOf 查找当前元素首次出现的位置是否等于当前索引,若相等则将其放入新数组。
function uniq4(arr) {
let res = []
for (let i = 0; i < arr.length; i++) {
if (arr.indexOf(arr[i]) === i) {
res.push(arr[i])
}
}
return res
}
// 去重结果
// [1,'1',{},{},{c:1},{c:1},{},{c:1},[],[],[1],[1],undefined,null]
同理,由于 NaN === NaN 为 false,使用 indexOf 查找 NaN 时结果始终为 -1,因此新数组中不会包含 NaN。
2.2.2 findIndex
利用 findIndex 查找当前元素首次出现的位置是否等于当前索引,若相等则将其放入新数组。
function uniq5(arr) {
let res = []
for (let i = 0; i < arr.length; i++) {
if (arr.findIndex(item => item === arr[i]) === i) {
res.push(arr[i])
}
}
return res
}
// 去重结果
// [1,'1',{},{},{c:1},{c:1},{},{c:1},[],[],[1],[1],undefined,null]
同理,由于 NaN === NaN 为 false,使用 findIndex 查找 NaN 时结果始终为 -1,因此新数组中不会包含 NaN。
2.3 元素是否存在型
此类方法通过判断新数组中是否已存在当前元素来实现去重。
2.3.1 includes
includes 方法用于判断数组是否包含指定的值。
function uniq6(arr) {
let res = []
for (let i = 0; i < arr.length; i++) {
if (!res.includes(arr[i])) {
res.push(arr[i])
}
}
return res
}
// 去重结果
// [1,'1',{},{},{c:1},{c:1},{},{c:1},[],[],[1],[1],undefined,null,NaN]
includes 采用零值相等算法来确定是否找到给定元素,因此可以正确判断 NaN 是否存在于新数组中。
2.3.2 some
some 方法用于测试数组中是否至少有一个元素通过了提供的函数测试。
function uniq7(arr) {
let res = []
for (let i = 0; i < arr.length; i++) {
if (!res.some(item => item === arr[i])) {
res.push(arr[i])
}
}
return res
}
// 去重结果
// [1,'1',{},{},{c:1},{c:1},{},{c:1},[],[],[1],[1],undefined,null,NaN,NaN]
此处仍使用 === 比较元素,由于 NaN === NaN 为 false,导致新数组中可能包含多个 NaN。
2.4 依托数据结构特性
此类方法利用 ES6 提供的 Map 和 Set 数据结构本身不可重复的特性来实现去重。
2.4.1 Map
ES6 提供的 Map 结构允许使用各种类型的值(包括对象)作为键,且键具有唯一性。
function uniq8(arr) {
const map = new Map()
for (let i = 0; i < arr.length; i++) {
!map.has(arr[i]) && map.set(arr[i], true)
}
return [...map.keys()]
}
// 去重结果
// [1,'1',{},{},{c:1},{c:1},{},{c:1},[],[],[1],[1],undefined,null,NaN]
map.has 方法对 NaN 同样有效。
2.4.2 Set(ES6 最常用)
Set 结构的成员值唯一,不存在重复值。
function uniq9(arr) {
return [...new Set(arr)]
}
// 去重结果
// [1,'1',{},{},{c:1},{c:1},{},{c:1},[],[],[1],[1],undefined,null,NaN]
3 补充
上述方法可通过不同的 Api 进行改造,例如在使用 splice 去除数组元素的地方,我们可以改用 filter 方法过滤数组以得到新数组。
再比如,在 includes 的方法中,若不使用 for 循环遍历数组,可通过 reduce 方法来替代。
总之,方法众多,但核心逻辑不变。
部分去重方法对 NaN 无效,因为 NaN === NaN 为 false;若有此需求,可使用 Object.is(NaN, NaN) 为 true 来进行修改。
实际应用中,最常用的方法是使用 Set,也可借助第三方库 lodash 来处理。
到此这篇关于JS数组去重详情的文章就介绍到这了,更多相关JS数组去重内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!