Xóa trùng mảng JavaScript | Set, filter, Map và khi nào dùng tool text
Primitive trong code → [...new Set(arr)]. Object trùng theo id/email → Map 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 runtime và ops 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ữ
| 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]" và "[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ống | Chọn | Lý do |
|---|---|---|
products.map trong React/Node | Set / Map | Nằm trong pipeline, cần test |
| File CSV 5.000 email từ Google Sheet | Tool dedupe | Không cần repo; xử lý local |
| API trả mảng user trùng id | Map by id | Giữ 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 / ETL | Code + unit test | Lặ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
indexOftrong 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.