首頁
學習紀錄
遊戲心得影視Life書單案件檔案
Side Projects委託作品與二創互動實驗場
Kurau
百百 BLOG
首頁
學習紀錄
遊戲心得影視Life書單案件檔案
Side Projects委託作品與二創互動實驗場
Kurau

Kurau Blog

「隨心而寫,真真假假,都是我」

一個記錄生活、輸出興趣的個人空間。
遊戲、影視、閱讀、學習……每一段體驗都值得留下文字。

頁面導覽

  • 學習紀錄
  • 遊戲心得
  • 影視Life
  • 書單
  • 委託作品與二創
  • Kurau
  • 合作邀請

找到我

歡迎來 Discord 找我聊天!

“曾經發生的事不可能忘記,只是暫時想不起來而已。”-《神隱少女》

© 2026 Kurau All rights reserved

面試考題

陣列arr1 = 1,1,2,2,3,3去重

By Kurau·Updated 2026-06-02·6 分鐘閱讀

放到Set去就好啦

題目看起來很簡單(一行 [...new Set(arr)] 就過了),但面試官真正想看的是你知不知道每種寫法的取捨:時間複雜度、能不能保留順序、遇到 NaN 跟物件參考會怎樣。把這幾個陷阱講清楚才是加分點。

方法一:Set + 展開(最簡潔,首選)

Set 本身就是「值唯一」的集合,丟進去再展開成陣列即可。

const arr1 = [1, 1, 2, 2, 3, 3];
const unique = [...new Set(arr1)];
console.log(unique); // [1, 2, 3]

// 等價寫法:Array.from 還能順便 map
const unique2 = Array.from(new Set(arr1));
js
  • 時間複雜度 O(n):Set 內部用 hash,add / has 平均 O(1)。
  • 保留順序:Set 依插入順序迭代,所以結果順序跟原陣列第一次出現的順序一致。
  • 去重判定用 SameValueZero:跟 === 幾乎一樣,但有一個關鍵差異 —— NaN 被視為等於自己(見下方陷阱)。

方法二:filter + indexOf

indexOf 回傳「第一次出現的索引」,只有當前索引等於它時才保留 → 自然去掉後面的重複。

const unique = arr1.filter((item, index) => arr1.indexOf(item) === index);
console.log(unique); // [1, 2, 3]
js
  • 時間複雜度 O(n²):filter 走 n 次,每次 indexOf 內部又是 O(n) 線性掃描。資料量大會明顯變慢。
  • 陷阱:indexOf 用嚴格相等 === 比對,找不到 NaN([NaN].indexOf(NaN) 回傳 -1),所以 NaN 會被全部濾掉而不是保留一個。要支援 NaN 改用 findIndex(x => Object.is(x, item))。
  • 好處:寫法直覺、不依賴 Set,相容極舊環境。

方法三:reduce

用一個 accumulator 陣列累積,沒看過的才 push。

const unique = arr1.reduce((acc, cur) => {
  if (!acc.includes(cur)) acc.push(cur);
  return acc;
}, []);
console.log(unique); // [1, 2, 3]
js
  • 時間複雜度 O(n²):includes 每次都線性掃 acc。跟 filter+indexOf 同級。
  • 真正的用途不是去重本身,而是去重的同時還要做別的事(轉型、聚合、計數),可以在 reduce 裡一次完成。單純去重沒必要繞 reduce。
  • includes 用 SameValueZero,所以能正確處理 NaN(跟 indexOf 不同這點常被問)。

方法四:Map(物件陣列依 key 去重)

原始型別用 Set 就夠了。但物件陣列不能直接丟 Set,因為 Set 比的是參考(reference),兩個內容一樣但不同 reference 的物件會被當作不同值:

const users = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' },
  { id: 1, name: 'Alice' }, // 內容相同但是「不同物件」
];

[...new Set(users)].length; // 3 —— Set 完全沒去到重!
js

正確做法:用 Map 以「業務上的唯一 key」(這裡是 id)當 key,後寫的會覆蓋先寫的,最後取 .values()。

const uniqueUsers = [...new Map(users.map(u => [u.id, u])).values()];
console.log(uniqueUsers);
// [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }]
js
想「保留第一筆」而不是「最後一筆」
new Map 在 key 重複時會用後面的值覆蓋,所以上面保留的是最後一個 id: 1。若要保留第一筆,先把陣列 reverse 再建 Map,或建 Map 時先判斷 if (!map.has(u.id)) map.set(u.id, u)。

陷阱整理(面試最愛問)

1. NaN 的去重行為

[...new Set([NaN, NaN])];                          // [NaN]  ✅ Set 認得 NaN
[NaN, NaN].filter((x, i) => [NaN].indexOf(x) === i); // []   ❌ indexOf 找不到 NaN
[NaN, NaN].reduce((a, c) => a.includes(c) ? a : [...a, c], []); // [NaN] ✅ includes 認得
js
比較方式用在哪NaN === NaN+0 / -0
=== (strict)indexOffalse相等
SameValueZeroSet、includestrue相等
SameValueObject.istrue不相等

重點記法:Set 跟 includes 都認得 NaN,只有 indexOf 不認得。

2. 物件去重比的是參考

如前述,Set / indexOf / includes 對物件都是比 reference,不是比內容。要按內容去重必須自己定義唯一鍵(單 key 用 Map;多 key 可用 JSON.stringify 當 key,但要注意 key 順序與不可序列化值的問題)。

3. 順序保留

四種方法全部都保留首次出現的順序(Set 依插入序、filter/reduce/Map 依遍歷序)。如果面試官追問「去重後要排序怎麼辦」,那是去重之後再 .sort(),兩件事分開做,不要混在去重邏輯裡。

總結對比

方法時間複雜度保留順序處理 NaN適用場景
Set + 展開O(n)✅✅原始型別,首選
filter + indexOfO(n²)✅❌ 會濾掉相容極舊環境 / 不想用 Set
reduceO(n²)✅✅去重同時要做額外邏輯
Map(依 key)O(n)✅—物件陣列依業務 key 去重

一句話收尾:原始型別 [...new Set(arr)],物件陣列 [...new Map(arr.map(o => [o.key, o])).values()],其餘寫法知道原理跟陷阱就好。

目錄

    ◆ 相關文章

    • 說說reduce() filter() map()的效果

      說說reduce() filter() map()的效果

      2026-05-09
    • Async function-Await 函式

      2026-06-02
    • throw Error用法

      2026-06-02
    • TypeScript 特性 - Interface

      2026-06-02
    ← 上一篇箭頭函數 Arrow function expression

    ◆ 關於作者

    Kurau

    個人寫作 / 創作的 SoT,記錄遊戲、影視、學習與生活。

    更多 Kurau 的文章