JS數(shù)據(jù)去重的7種實(shí)用方法總結(jié)(附完整代碼與原理)
前言
在 JavaScript 開發(fā)中,“數(shù)據(jù)去重” 是高頻需求 —— 比如處理接口返回的重復(fù)列表、用戶輸入的重復(fù)值等。不同場(chǎng)景下,適合的去重方法不同,有的追求簡潔,有的追求性能,有的需要兼容復(fù)雜數(shù)據(jù)類型。今天就整理 7 種常用的去重方法,每種都附帶代碼和核心原理,幫你輕松應(yīng)對(duì)各種去重場(chǎng)景。
一、基礎(chǔ)方法:利用 Set 去重(最簡潔)
核心原理:ES6 新增的 Set 集合本身具有 “值唯一” 的特性,能自動(dòng)忽略重復(fù)值,再通過擴(kuò)展運(yùn)算符(...)或 Array.from() 轉(zhuǎn)回?cái)?shù)組即可。
代碼實(shí)現(xiàn)
// 待去重?cái)?shù)組(包含數(shù)字、字符串、重復(fù)值) const arr = [1, 2, 2, "3", "3", 4, 4, 5]; // 方法 1:擴(kuò)展運(yùn)算符轉(zhuǎn)數(shù)組 const uniqueArr1 = [...new Set(arr)]; // 方法 2:Array.from() 轉(zhuǎn)數(shù)組(兼容不支持?jǐn)U展運(yùn)算符的環(huán)境) const uniqueArr2 = Array.from(new Set(arr)); console.log(uniqueArr1); // 輸出:[1, 2, "3", 4, 5] console.log(uniqueArr2); // 輸出:[1, 2, "3", 4, 5]
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):代碼最簡潔,一行搞定;性能優(yōu)秀(底層是哈希表實(shí)現(xiàn),時(shí)間復(fù)雜度 O (n));
缺點(diǎn):無法區(qū)分 “值相同但類型不同” 的數(shù)據(jù)(比如
1和"1"會(huì)被視為不同值,這其實(shí)是合理的,若需強(qiáng)制相同需額外處理);不支持對(duì)象 / 數(shù)組等引用類型的去重(引用不同會(huì)被視為不同值)。
適用場(chǎng)景
處理簡單數(shù)據(jù)類型(數(shù)字、字符串、布爾值)的數(shù)組,追求代碼簡潔和性能。
二、傳統(tǒng)方法:利用 indexOf/includes 去重
核心原理:創(chuàng)建一個(gè)新數(shù)組,遍歷原數(shù)組,判斷當(dāng)前元素是否在新數(shù)組中(用 indexOf 或 includes),不在則加入新數(shù)組,最終得到去重后的數(shù)組。
代碼實(shí)現(xiàn)(indexOf 版)
const arr = [1, 2, 2, 3, 3, 3];
const uniqueArr = [];
for (let i = 0; i < arr.length; i++) {
// indexOf 返回 -1 表示元素不在新數(shù)組中
if (uniqueArr.indexOf(arr[i]) === -1) {
uniqueArr.push(arr[i]);
}
}
console.log(uniqueArr); // 輸出:[1, 2, 3]代碼實(shí)現(xiàn)(includes 版,更直觀)
const arr = [1, 2, 2, 3, 3, 3];
const uniqueArr = [];
for (let item of arr) {
// includes 直接返回布爾值,判斷元素是否存在
if (!uniqueArr.includes(item)) {
uniqueArr.push(item);
}
}
console.log(uniqueArr); // 輸出:[1, 2, 3]優(yōu)缺點(diǎn)
優(yōu)點(diǎn):兼容性好(支持 ES5 及以上,無需擔(dān)心環(huán)境問題);邏輯直觀,容易理解;
缺點(diǎn):性能較差(
indexOf/includes每次都會(huì)遍歷新數(shù)組,時(shí)間復(fù)雜度 O (n²),數(shù)據(jù)量大時(shí)會(huì)卡頓);同樣不支持引用類型去重。
適用場(chǎng)景
處理小規(guī)模的簡單類型數(shù)組,或需要兼容低版本瀏覽器的場(chǎng)景。
三、優(yōu)化性能:利用對(duì)象鍵名唯一性去重
核心原理:對(duì)象的鍵名(key)具有唯一性(不能重復(fù)),遍歷原數(shù)組時(shí),把元素作為對(duì)象的鍵名存儲(chǔ),同時(shí)判斷鍵名是否已存在,不存在則加入新數(shù)組。
代碼實(shí)現(xiàn)
const arr = [1, 2, 2, "2", 3, 3];
const uniqueArr = [];
const tempObj = {}; // 臨時(shí)對(duì)象,用于存儲(chǔ)已存在的元素
for (let item of arr) {
// 把元素轉(zhuǎn)為字符串作為鍵名(避免 1 和 "1" 被視為不同鍵)
const key = typeof item + item;
if (!tempObj[key]) {
tempObj[key] = true; // 標(biāo)記為已存在
uniqueArr.push(item);
}
}
console.log(uniqueArr); // 輸出:[1, 2, "2", 3]關(guān)鍵細(xì)節(jié)
為什么要加
typeof item?比如1(數(shù)字)和"1"(字符串),直接用item當(dāng)鍵名會(huì)都是"1",導(dǎo)致誤判;加typeof后,鍵名會(huì)變成"number1"和"string1",能正確區(qū)分。若想強(qiáng)制把
1和"1"視為相同值,可去掉typeof,直接用const key = item。
優(yōu)缺點(diǎn)
優(yōu)點(diǎn):性能優(yōu)秀(對(duì)象的鍵名查詢是哈希表操作,時(shí)間復(fù)雜度 O (n));支持區(qū)分 “值相同類型不同” 的場(chǎng)景;
缺點(diǎn):需額外處理鍵名類型(避免誤判);不支持引用類型去重(對(duì)象作為鍵名會(huì)被轉(zhuǎn)為
[object Object],導(dǎo)致所有對(duì)象都被視為相同)。
適用場(chǎng)景
處理大規(guī)模的簡單類型數(shù)組,追求高性能,且需要區(qū)分值類型。
四、進(jìn)階方法:利用 filter + indexOf 去重
核心原理:filter 方法會(huì)篩選出滿足條件的元素,結(jié)合 indexOf 判斷 “當(dāng)前元素在原數(shù)組中的第一次出現(xiàn)位置是否等于當(dāng)前索引”—— 如果等于,說明是第一次出現(xiàn),保留;否則是重復(fù)值,過濾掉。
代碼實(shí)現(xiàn)
const arr = [1, 2, 2, 3, 3, 4];
// filter 回調(diào)函數(shù):返回 true 則保留元素
const uniqueArr = arr.filter((item, index) => {
// indexOf 會(huì)返回元素在數(shù)組中第一次出現(xiàn)的索引
return arr.indexOf(item) === index;
});
console.log(uniqueArr); // 輸出:[1, 2, 3, 4]優(yōu)缺點(diǎn)
優(yōu)點(diǎn):代碼簡潔(一行搞定);邏輯優(yōu)雅,適合函數(shù)式編程風(fēng)格;
缺點(diǎn):性能差(
filter遍歷一次,indexOf每次又遍歷一次,時(shí)間復(fù)雜度 O (n²));不支持引用類型去重。
適用場(chǎng)景
處理小規(guī)模簡單類型數(shù)組,追求函數(shù)式編程風(fēng)格,不關(guān)心極致性能。
五、ES6 進(jìn)階:利用 Map 去重
核心原理:Map 的鍵(key)可以是任意類型,且具有唯一性。遍歷原數(shù)組時(shí),用 Map.has() 判斷元素是否已存在,不存在則用 Map.set() 存儲(chǔ),并加入新數(shù)組。
代碼實(shí)現(xiàn)
const arr = [1, 2, 2, "3", "3", 4];
const uniqueArr = [];
const tempMap = new Map();
for (let item of arr) {
if (!tempMap.has(item)) { // 判斷元素是否已在 Map 中
tempMap.set(item, true); // 存儲(chǔ)元素(值隨便設(shè),只要有標(biāo)記即可)
uniqueArr.push(item);
}
}
console.log(uniqueArr); // 輸出:[1, 2, "3", 4]優(yōu)缺點(diǎn)
優(yōu)點(diǎn):性能優(yōu)秀(時(shí)間復(fù)雜度 O (n));支持更多鍵類型(比如
NaN,Set也支持,但indexOf不支持NaN的判斷);缺點(diǎn):代碼比
Set稍繁瑣;不支持引用類型去重(引用不同會(huì)被視為不同鍵)。
適用場(chǎng)景
處理簡單類型數(shù)組,或需要存儲(chǔ) NaN 等特殊值的場(chǎng)景(indexOf 無法判斷 NaN,但 Map.has(NaN) 可以)。
六、復(fù)雜場(chǎng)景:引用類型(對(duì)象 / 數(shù)組)去重
前面的方法都無法處理對(duì)象 / 數(shù)組等引用類型(比如 { id: 1 } 和 { id: 1 } 會(huì)被視為不同值,因?yàn)橐貌煌?。此時(shí)需要通過 “比較內(nèi)容” 來實(shí)現(xiàn)去重,常用方法是 JSON.stringify 轉(zhuǎn)字符串 或 手動(dòng)比較屬性。
方法 1:JSON.stringify 轉(zhuǎn)字符串(簡單對(duì)象適用)
核心原理:把對(duì)象轉(zhuǎn)為 JSON 字符串,再用 Set 或?qū)ο箧I名去重(字符串可以被唯一識(shí)別)。
// 待去重的對(duì)象數(shù)組(id 相同視為重復(fù))
const arr = [
{ id: 1, name: "小明" },
{ id: 1, name: "小明" }, // 重復(fù)
{ id: 2, name: "小紅" }
];
const uniqueArr = [...new Set(arr.map(item => JSON.stringify(item)))]
.map(str => JSON.parse(str)); // 轉(zhuǎn)回對(duì)象
console.log(uniqueArr);
// 輸出:[{ id: 1, name: "小明" }, { id: 2, name: "小紅" }]方法 2:手動(dòng)比較屬性(復(fù)雜對(duì)象適用)
核心原理:指定一個(gè)唯一標(biāo)識(shí)(比如 id),遍歷數(shù)組時(shí),判斷新數(shù)組中是否已有該標(biāo)識(shí)的對(duì)象,沒有則加入。
const arr = [
{ id: 1, name: "小明" },
{ id: 1, name: "小明" },
{ id: 2, name: "小紅" }
];
const uniqueArr = [];
for (let item of arr) {
// 用 some 判斷新數(shù)組中是否已有相同 id 的對(duì)象
const isRepeat = uniqueArr.some(uniqueItem => uniqueItem.id === item.id);
if (!isRepeat) {
uniqueArr.push(item);
}
}
console.log(uniqueArr);
// 輸出:[{ id: 1, name: "小明" }, { id: 2, name: "小紅" }]優(yōu)缺點(diǎn)
優(yōu)點(diǎn):能處理引用類型的去重;
缺點(diǎn):
JSON.stringify有局限性(比如無法處理function、undefined、Symbol等屬性);手動(dòng)比較屬性需指定唯一標(biāo)識(shí),靈活性較低。
適用場(chǎng)景
處理對(duì)象數(shù)組,且對(duì)象結(jié)構(gòu)簡單(無特殊屬性),或有明確唯一標(biāo)識(shí)(如 id)的場(chǎng)景。
七、排序后去重:利用 sort + 相鄰比較
核心原理:先通過 sort() 把數(shù)組排序(相同元素會(huì)相鄰),再遍歷排序后的數(shù)組,比較當(dāng)前元素和前一個(gè)元素,不同則加入新數(shù)組。
代碼實(shí)現(xiàn)
const arr = [3, 1, 2, 2, 3, 1, 4];
const uniqueArr = [];
// 先排序(注意:sort 默認(rèn)按字符串排序,數(shù)字需加比較函數(shù))
const sortedArr = arr.sort((a, b) => a - b); // 排序后:[1, 1, 2, 2, 3, 3, 4]
for (let i = 0; i < sortedArr.length; i++) {
// 比較當(dāng)前元素和前一個(gè)元素,不同則保留
if (sortedArr[i] !== sortedArr[i - 1]) {
uniqueArr.push(sortedArr[i]);
}
}
console.log(uniqueArr); // 輸出:[1, 2, 3, 4]優(yōu)缺點(diǎn)
優(yōu)點(diǎn):邏輯簡單,適合已排序或可排序的數(shù)組;
缺點(diǎn):會(huì)改變?cè)瓟?shù)組的順序(若需保留原順序則不適用);性能受排序影響(
sort時(shí)間復(fù)雜度約 O (n log n));不支持引用類型去重。
適用場(chǎng)景
處理數(shù)字?jǐn)?shù)組,且不關(guān)心原數(shù)組順序的場(chǎng)景。
總結(jié):不同場(chǎng)景如何選?
| 場(chǎng)景需求 | 推薦方法 | 時(shí)間復(fù)雜度 |
|---|---|---|
| 簡單類型 + 代碼簡潔 | Set 去重 | O(n) |
| 簡單類型 + 高性能 + 大規(guī)模 | 對(duì)象鍵名去重 / Map 去重 | O(n) |
| 低版本瀏覽器兼容 | indexOf/includes 去重 | O(n²) |
| 函數(shù)式編程風(fēng)格 | filter + indexOf 去重 | O(n²) |
| 對(duì)象數(shù)組(有唯一標(biāo)識(shí)) | 手動(dòng)比較屬性去重 | O(n²) |
| 數(shù)字?jǐn)?shù)組(不關(guān)心順序) | sort + 相鄰比較去重 | O(n log n) |
記住:沒有 “最好” 的去重方法,只有 “最適合” 的 —— 根據(jù)數(shù)據(jù)類型、數(shù)據(jù)規(guī)模和環(huán)境需求選擇即可!
到此這篇關(guān)于JS數(shù)據(jù)去重的7種實(shí)用方法總結(jié)的文章就介紹到這了,更多相關(guān)JS數(shù)據(jù)去重方法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Javascript oop設(shè)計(jì)模式 面向?qū)ο缶幊毯唵螌?shí)例介紹
這篇文章主要介紹了Javascript oop設(shè)計(jì)模式 面向?qū)ο缶幊毯唵螌?shí)例介紹的相關(guān)資料,這里附有實(shí)例代碼幫助大家學(xué)習(xí)理解,需要的朋友可以參考下2016-12-12
Javascript取整函數(shù)及向零取整幾種常用的方法
這篇文章主要介紹了Javascript取整函數(shù)及向零取整幾種常用的方法,每種方法都有其特點(diǎn)和適用場(chǎng)景,推薦使用Math.trunc(),因?yàn)樗Z義明確、代碼易讀且性能較好,需要的朋友可以參考下2025-01-01
javascript實(shí)現(xiàn)固定側(cè)邊欄
這篇文章主要為大家詳細(xì)介紹了javascript實(shí)現(xiàn)固定側(cè)邊欄,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-02-02
一文帶你徹底搞懂JS大文件分片上傳的實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了前端JavaScript是如何實(shí)現(xiàn)大文件分片上傳的,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價(jià)值,需要的可以參考一下2023-05-05
BootStrap 動(dòng)態(tài)添加驗(yàn)證項(xiàng)和取消驗(yàn)證項(xiàng)的實(shí)現(xiàn)方法
這篇文章主要介紹了BootStrap 動(dòng)態(tài)添加驗(yàn)證項(xiàng)和取消驗(yàn)證項(xiàng)的實(shí)現(xiàn)方法的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09

