数组去重本质是保留首次出现元素,核心在于判断重复的逻辑:Set用SameValueZero算法(NaN视为相同,对象按内存地址区分);filter+indexOf用===比较且无法识别NaN;includes可识别NaN但效率低;对象键名法会强制转字符串导致类型混淆。

数组去重,本质就是“只留第一次出现的,后面重复的都不要”。关键不在“删”,而在“怎么判断它是不是重复”——这取决于你用什么方法,底层判断逻辑完全不同。
Set 方法:靠“自动拒收”实现去重
你可以把 Set 想成一个特别较真的收件柜:每个格子只能放一个东西,而且只认“完全一样”的才算重复。它内部用的是 SameValueZero 算法,好处是:
• 数字 1 和字符串 "1" 不算一样(类型不同);
• NaN === NaN 是 false,但 Set 会把多个 NaN 当作重复,只留一个;
• 两个 {} 虽然长得一样,但内存地址不同,Set 就认为是两个不同的东西,都会收下。
所以 Set 去重快又干净,但只对原始值真正“管用”。
filter + indexOf 方法:靠“查户口”确认身份
这个组合就像派一个人逐个问:“你第一次出现在哪?”
• 对每个元素 el,调用 arr.indexOf(el),它返回该值在数组里**最早出现的位置**;
• 如果这个位置刚好等于当前遍历的索引 i,说明这是它头一回露面,就留下;
• 否则,说明前面已经见过了,跳过。
注意:indexOf 用的是严格相等 ===,所以它也分不清 NaN(NaN.indexOf(NaN) === -1),遇到 NaN 就失效。
includes / for + 新数组 方法:靠“翻记录本”比对
想象你边走边记账:每看到一个新元素,就翻开手里的小本子(新数组)查一遍有没有记过。
• includes() 和 indexOf() !== -1 都是在做这件事,只是写法更直白;
• 它们同样依赖 SameValueZero,所以 includes 能正确识别 NaN,而 indexOf 不能;
• 缺点是每次都要从头扫一遍小本子,数据一多就慢(时间复杂度 O(n²))。
对象键名法:靠“字符串化”强行归类(慎用)
这个方法把数组元素当作文档编号,往一个空对象里存:
• obj[item] = true;
• 下次再遇到相同 item,赋值时不会报错,但 obj 中对应键已存在;
• 问题来了:所有键最终都是字符串!1、"1"、true 都变成 "1",null 和 undefined 都变成 "null" 或 "undefined",直接串味。
所以它不是真去重,是“强制转字符串后去重”,仅适合你明确知道数据全是数字且无歧义的极简场景。


















