JavaScript數組去重終極指南:從基礎到高級的多種方法(附面試題解析)
在前端開發中,數組去重是JavaScript中最常見的需求之一。本文將全面解析8種數組去重方法,包括基礎實現、ES6新特性、性能優化等,并附上面試常見問題解析。
一、基礎去重方法(ES5及以前)
-
雙重循環法
最基礎的去重方法,通過兩層循環比較元素:function unique(arr) {const result = [];for (let i = 0; i < arr.length; i++) {let isDuplicate = false;for (let j = 0; j < result.length; j++) {if (arr[i] === result[j]) {isDuplicate = true;break;}}if (!isDuplicate) result.push(arr[i]);}return result; }// 示例 console.log(unique([1, 2, 2, 3])); // [1, 2, 3]
時間復雜度:O(n2)
適用場景:小型數組,兼容性要求高 -
indexOf優化法
使用indexOf
替代內層循環:function unique(arr) {const result = [];for (let i = 0; i < arr.length; i++) {if (result.indexOf(arr[i]) === -1) {result.push(arr[i]);}}return result; }
注意:
indexOf
內部也是循環,本質上還是O(n2)復雜度 -
排序相鄰比較法
先排序后比較相鄰元素:function unique(arr) {arr.sort();const result = [arr[0]];for (let i = 1; i < arr.length; i++) {if (arr[i] !== arr[i-1]) {result.push(arr[i]);}}return result; }
缺點:會改變原數組順序,不適用于對象類型
二、ES6+ 高效去重方法
-
Set數據結構法(最常用)
ES6的Set自動處理唯一值:const unique = arr => [...new Set(arr)];// 示例 console.log(unique([1, 2, 2, '2', NaN, NaN])); // [1, 2, "2", NaN]
特點:
- 代碼最簡潔(9個字符最短實現)
- 支持
NaN
去重(NaN === NaN
為false,但Set能識別) - 時間復雜度和空間復雜度均為O(n)
-
Map數據結構法
利用Map的鍵唯一性:function unique(arr) {const map = new Map();return arr.filter(item => !map.has(item) && map.set(item, true)); }
優勢:保留原始順序,適合需要順序的場景
-
filter + indexOf
利用索引位置判斷:const unique = arr => arr.filter((item, index) => arr.indexOf(item) === index);
缺點:
indexOf
無法識別NaN
,時間復雜度O(n2) -
reduce累積法
使用reduce實現優雅去重:const unique = arr => arr.reduce((acc, cur) => acc.includes(cur) ? acc : [...acc, cur], [] );
三、特殊場景處理
-
對象數組去重
根據對象屬性去重:function uniqueByKey(arr, key) {const map = new Map();return arr.filter(item => {const identifier = item[key];return !map.has(identifier) && map.set(identifier, true);}); }// 示例 const users = [{id: 1, name: 'Alice'},{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'} ]; console.log(uniqueByKey(users, 'id')); // 兩個Alice對象只會保留一個
-
多維數組去重
遞歸處理嵌套數組:function deepUnique(arr) {const flatArr = arr.flat(Infinity);return [...new Set(flatArr)]; }// 示例 console.log(deepUnique([1, [2, [3, 2]], 4])); // [1, 2, 3, 4]
-
混合類型精確去重
區分數字1和字符串’1’:function strictUnique(arr) {const seen = new Map();return arr.filter(item => {const type = typeof item;const key = `${type}-${item}`;return !seen.has(key) && seen.set(key, true);}); }// 示例 console.log(strictUnique([1, '1', 2, 2])); // [1, "1", 2]
四、面試高頻問題與陷阱解析
-
問題:Set去重有什么缺陷?
答:- 不區分
-0
和+0
(Set認為它們相等) - 對象內容相同但引用不同時無法去重
const obj = {a: 1}; console.log(unique([obj, obj, {a: 1}])); // [obj, {a:1}] → 兩個不同對象
- 不區分
-
問題:如何實現O(n)時間復雜度的去重?
答:使用Set或Map數據結構,它們的查找操作是O(1)復雜度 -
陷阱:indexOf無法識別NaN
[NaN].indexOf(NaN) // -1 → 無法識別 [...new Set([NaN, NaN])] // [NaN] → 正確識別
-
問題:如何保留去重后的原始順序?
答:使用Map或對象記錄首次出現位置:function orderedUnique(arr) {return [...new Map(arr.map(item => [item, item])).values()]; }
-
性能大比拼(10,000個元素測試):
方法 耗時(ms) 可讀性 適用場景 雙重循環 1200 ★★☆ 小型數組 Set 2.5 ★★★★★ 現代瀏覽器 filter + indexOf 850 ★★★☆ 簡單去重 Map 3.0 ★★★★☆ 需保留順序的場景
五、去重方法總結表
方法 | 代碼復雜度 | 時間復雜度 | 特殊類型支持 | 適用場景 |
---|---|---|---|---|
雙重循環 | 高 | O(n2) | 所有類型 | 兼容性要求高的老項目 |
Set | 極低 | O(n) | ? NaN | 現代項目首選 |
Map | 中 | O(n) | ? 對象引用 | 需要保留順序的場景 |
filter + indexOf | 低 | O(n2) | ? NaN | 簡單數組去重 |
reduce | 中 | O(n2) | ? NaN | 函數式編程場景 |
排序相鄰比較 | 中 | O(n log n) | ? 混合類型 | 純數字/字符串數組 |
對象屬性去重 | 高 | O(n) | ? 按指定屬性去重 | 對象數組去重 |
六、最佳實踐與使用建議
-
現代項目首選
// 99%場景使用Set足夠 const unique = arr => [...new Set(arr)];
-
需要兼容IE時
// 使用對象+類型標記的polyfill function legacyUnique(arr) {const seen = {};return arr.filter(item => {const key = typeof item + JSON.stringify(item);return seen.hasOwnProperty(key) ? false : (seen[key] = true);}); }
-
大數組優化
對于超過10,000個元素的數組:function bigArrayUnique(arr) {const set = new Set();const result = [];for (let i = 0; i < arr.length; i++) {if (!set.has(arr[i])) {set.add(arr[i]);result.push(arr[i]);}}return result; }
-
TypeScript強化版
function typedUnique<T>(arr: T[]): T[] {return Array.from(new Set(arr)); }// 對象數組按key去重 function uniqueByKey<T>(arr: T[], key: keyof T): T[] {const map = new Map<any, T>();arr.forEach(item => {const identifier = item[key];if (!map.has(identifier)) {map.set(identifier, item);}});return Array.from(map.values()); }
掌握這些去重方法,不僅能輕松應對面試,更能根據實際場景選擇最優方案。記住:當簡單方案(如Set)能滿足需求時,不要過度設計。建議在項目中統一封裝去重工具函數。