Xóa trùng mảng JavaScript | Set, filter, Map và khi nào dùng tool text

(Cập nhật: 19 tháng 7, 2026 ) JavaScript Set dedupe mảng trùng lặp email list
Kết luận

Primitive trong code → [...new Set(arr)]. Object trùng theo id/emailMap hoặc Set các key đã thấy, không tin Set trên object. Danh sách email/CSV từ Excel → tool text Xóa trùng lặp (chạy local), không viết script một lần rồi quên edge case. Nhớ: NaN được gộp, hai object giống nội dung không gộp.

Junior paste filter((v,i,a) => a.indexOf(v) === i) vào mọi chỗ. Senior hỏi: dữ liệu là gì — số, object, hay file CSV 8.000 email từ marketing? Câu trả lời quyết định Set, Map, hay tool dòng text.

Freelancer / intern outsourcing VN hay nhận ticket “làm sạch list khách” lúc 5 giờ chiều — không phải lúc viết thuật toán. Bài này tách code runtimeops trên text, kèm pitfall NaN / reference.

Bài viết này giúp bạn

  • Chọn Set / filter / Map theo kiểu phần tử
  • Hiểu SameValueZero (vì sao NaN “biến mất trùng”)
  • Dedupe object theo key đúng cách
  • Biết khi nào mở tool xóa trùng thay vì node
  • Checklist trước khi merge PR dedupe

Ba cách trong JavaScript

1. Set (mặc định cho primitive)

const ids = [1, 2, 2, 3, 1, NaN, NaN];
const unique = [...new Set(ids)];
// [1, 2, 3, NaN]  — một NaN duy nhất

Set giữ thứ tự chèn (ES2015+) và gần O(n). Đây là câu trả lời đúng cho “xóa trùng số / chuỗi nhanh nhất”.

2. filter + indexOf (tránh trên list lớn)

const unique = arr.filter((value, index, self) => self.indexOf(value) === index);

Mỗi phần tử quét lại mảng → O(n²). Vài chục phần tử ổn; vài chục nghìn (log ID, SKU) sẽ đơ tab. Chỉ giữ khi cần polyfill cực kỳ cũ hoặc điều kiện đặc biệt không gắn Set được.

3. Map / seen Set cho object

const users = [
  { id: 1, name: "An" },
  { id: 2, name: "Bình" },
  { id: 1, name: "An (cập nhật)" },
];

const byId = [...new Map(users.map((u) => [u.id, u])).values()];
// [{ id: 1, name: "An (cập nhật)" }, { id: 2, name: "Bình" }]
// Map ghi đè → bản SAU cùng id thắng

Giữ bản đầu tiên:

function uniqueBy(arr, keyFn) {
  const seen = new Set();
  return arr.filter((item) => {
    const k = keyFn(item);
    if (seen.has(k)) return false;
    seen.add(k);
    return true;
  });
}

uniqueBy(users, (u) => u.id);
// bản đầu id:1 được giữ
Set vs filter vs Map
Cách Phù hợp Cạm bẫy
Set number, string, boolean Object = theo reference; không ‘deep equal’
filter + indexOf List nhỏ / legacy O(n²); NaN: indexOf(NaN) luôn -1 → giữ mọi NaN
Map / seen Set Object theo id, email, slug Phải chọn key; quyết định giữ first vs last

Pitfall: NaN, object reference, so sánh “bằng mắt”

NaN

[NaN, NaN].filter((v, i, a) => a.indexOf(v) === i);
// [NaN, NaN]  — indexOf không tìm thấy NaN

[...new Set([NaN, NaN])];
// [NaN]       — SameValueZero coi hai NaN là một

Nếu pipeline xử lý số đo / sensor và dùng filter+indexOf, bạn có thể nhân bản NaN mà không hay biết.

Object reference

const a = { id: 1 };
const b = { id: 1 };
[...new Set([a, b])]; // [a, b] — hai object khác nhau
[...new Set([a, a])]; // [a]    — cùng reference

Designer Figma export JSON trùng id ≠ trùng reference trong JS. Luôn dedupe theo business key.

Chuỗi “trông giống”

"[email protected]""[email protected]" là hai phần tử với Set mặc định. List email marketing thường cần toLowerCase() + trim() trước khi unique — trong code hoặc bằng option trim/case trên tool text.


Code vs tool text — quyết định 30 giây

Tình huốngChọnLý do
products.map trong React/NodeSet / MapNằm trong pipeline, cần test
File CSV 5.000 email từ Google SheetTool dedupeKhông cần repo; xử lý local
API trả mảng user trùng idMap by idGiữ last/first có chủ đích
Keyword SEO 1 dòng 1 từTool text hoặc script nhỏThường one-off
Deduplicate trong CI / ETLCode + unit testLặp lại, audit được

Tool Xóa trùng lặp Kawa nhận 1 dòng = 1 mục, xóa dòng trùng, có thể chọn giữ lần xuất hiện đầu/cuối và trim khoảng trắng. Dữ liệu không gửi server — phù hợp list khách hàng thật khi bạn không muốn paste vào random SaaS.

🧹 Xóa trùng lặp ngay tại đây

Không dùng tool text để “fix” mảng object trong production. Tool không hiểu {id:1}; nó chỉ so khớp chuỗi dòng.


Case study ngắn

A — Intern: list ID đơn hàng

Nhận orderIds: number[] từ webhook đôi khi double-fire. [...new Set(orderIds)] trong handler là đủ. Thêm log before/after length để chứng minh với PM.

B — Freelance: email campaign SME

Khách gửi Excel 3 sheet gộp — khoảng 12.000 dòng, nhiều khoảng trắng đuôi. Export CSV → dán dedupe với trim → copy unique vào Mailchimp. Viết Node script cho việc một lần thường tốn hơn và dễ quên normalize case.

C — Bug production: “Set không xóa trùng user”

Set(users) giữ 200 object vì mỗi lần parse JSON tạo object mới. Fix: uniqueBy(users, u => u.email.toLowerCase()). Ticket đóng trong 20 phút sau khi hiểu reference.


Checklist trước khi merge

  • Phần tử là primitive hay object?
  • Key business là gì (id, email, sku)?
  • Giữ first hay last khi trùng?
  • Có cần trim / toLowerCase?
  • n có thể > 10.000? → tránh indexOf trong filter
  • Có unit test với NaN, null, object trùng key?
  • One-off list text? → tool local thay vì commit script throwaway

FAQ nhanh trong đầu

lodash _.uniq / _.uniqBy? Ổn nếu project đã phụ thuộc lodash — uniqBy đúng bài object. Đừng thêm lodash chỉ vì một dòng Set.

TypeScript? [...new Set(arr)] giữ union type; với object hãy gõ rõ Map<string, User>.

Sắp xếp sau dedupe? Set giữ insertion order. Cần sort → unique.sort(...) sau, đừng trộn logic.


Liên quan

  • Tool: Xóa trùng lặp (dedupe) — danh sách dòng, chạy trên trình duyệt
  • Khi đã sạch list, đếm ký tự / dòng: các utility text khác trên Kawa Dev Tools
  • Tối ưu bundle nếu copy lodash chỉ để uniq: cân nhắc native Set

Bookmark lý do: một trang quyết định Set hay Map hay paste tool — và giải thích vì sao object/NaN làm PR “dedupe” vẫn ship bug.