- 搞不定JavaScript的数组去重?你可能漏了这两个坑*
引言
数组去重是JavaScript开发中的常见需求,看似简单,却暗藏玄机。许多开发者认为用Set或filter就能轻松搞定,但实际场景中可能会遇到意想不到的"坑"。本文将从底层原理出发,深入剖析两个容易被忽略的数组去重问题:引用类型的去重 和特殊值的去重。通过对比不同方法的优劣,帮助你写出更健壮的代码。
主体
1. 基本方法回顾:为什么Set和filter有时会失效?
最常见的数组去重方法是使用Set或filter结合indexOf:
javascript
// 方法1:Set
const uniqueArray = [...new Set(array)];
// 方法2:filter + indexOf
const uniqueArray = array.filter((item, index) => array.indexOf(item) === index);
这两种方法对基本类型(如number、string、boolean)表现良好,但在以下场景中会失效:
- 引用类型去重 :
Set和indexOf通过严格相等(===)比较,两个内容相同的对象会被视为不同。 - 特殊值去重 :
NaN、null、undefined等值在去重时可能表现异常。
2. 第一个坑:引用类型的去重
假设数组包含对象或数组:
javascript
const arr = [{ a: 1 }, { a: 1 }, [1, 2], [1, 2]];
const uniqueArr = [...new Set(arr)]; // 无效!
此时,Set无法去重,因为{ a: 1 } === { a: 1 }为false。
解决方案:深度比较或序列化
- 深度比较 :使用
lodash的isEqual或自定义递归比较函数,但性能较差。 - 序列化:将对象转为JSON字符串去重,再转回对象:
javascript
const uniqueArr = Array.from(
new Set(arr.map(item => JSON.stringify(item)))
).map(item => JSON.parse(item));
- 局限性*:
- 对象属性顺序变化会导致误判(
{a:1, b:2}和{b:2, a:1}被视为不同)。 - 无法处理包含循环引用的对象。
3. 第二个坑:特殊值的去重
JavaScript中某些值的比较行为特殊:
NaN:NaN === NaN为false,导致Set和indexOf无法去重。+0和-0:+0 === -0为true,但某些场景需要区分。null和undefined:需明确是否需要区分。
解决方案:自定义比较函数
针对NaN:
javascript
const arr = [1, NaN, 2, NaN];
const uniqueArr = arr.filter((item, index) => {
return arr.findIndex(i => (Number.isNaN(i) && Number.isNaN(item)) || i === item) === index;
});
针对+0和-0:
javascript
const arr = [+0, -0, 0];
const uniqueArr = [...new Set(arr)]; // 无法区分
const uniqueArr = arr.filter((item, index) => {
return arr.findIndex(i => Object.is(i, item)) === index; // 区分+0和-0
});
4. 性能与兼容性权衡
| 方法 | 优点 | 缺点 |
|---|---|---|
Set |
简洁、高效 | 不兼容IE11,无法处理引用类型 |
filter + indexOf |
兼容性好 | 时间复杂度O(n²),性能差 |
| 序列化 | 支持引用类型 | 性能低,有局限性 |
| 自定义比较 | 灵活、精准 | 实现复杂 |
- 现代浏览器 :优先使用
Set。 - 兼容性要求高 :结合
filter和findIndex。 - 复杂场景 :使用
lodash的uniqBy或uniqWith。
5. 最佳实践
- 明确需求 :是否需要区分
+0/-0?是否需要深度比较对象? - 性能优化:大数据量时避免递归或嵌套循环。
- 测试覆盖 :针对
NaN、null、引用类型编写单元测试。
总结
数组去重并非"一招鲜"问题。Set和filter虽简单,但无法覆盖引用类型和特殊值的场景。理解JavaScript的相等比较机制(===、Object.is、JSON.stringify)是解决问题的关键。在实际开发中,应根据需求选择最适合的方法,并通过测试确保逻辑正确性。
希望本文能帮助你避开这两个"坑",写出更健壮的数组去重代码!