Khám phá Map và Set trong JavaScript: Hướng dẫn chi tiết tuần 2

2026-07-14 · Tin-Tuc · 읽는데 20분

Map và Set là gì? Câu chuyện từ một lần "vỡ mặt" vì mảng

Hồi mới tập tành code JavaScript, tôi từng có một trải nghiệm nhớ đời. Chuyện là tôi xây một ứng dụng quản lý danh sách người dùng đăng ký sự kiện.

Dùng mảng (array) để lưu, xong rồi cứ mỗi lần kiểm tra xem một người đã đăng ký chưa là lại phải chạy vòng lặp for hoặc dùng indexOf. Mà dữ liệu lên tới vài nghìn bản ghi, cảm giác như chờ đợi món phở trưa ở quán đông khách vậy – chậm rãi và bực mình.

Rồi một ngày, tôi được đồng nghiệp "khai sáng" về Map và Set. Lúc đầu còn hoài nghi: "Ủa, có gì khác mảng đâu?" Nhưng sau khi test thử, tôi thấy ngay sự khác biệt.

Map cho phép lưu trữ cặp key-value với bất kỳ kiểu dữ liệu nào, còn Set thì giống như một cái túi chỉ chứa những phần tử độc nhất. Quan trọng nhất: tốc độ tìm kiếm trong Map và Set ở mức O(1), trong khi mảng là O(n).

Có nghĩa là với 10.000 phần tử, mảng có thể mất tới 10.000 bước để tìm, nhưng Map và Set chỉ mất 1 bước. Hãy nhìn vào bảng so sánh dưới đây:

Đặc điểmMảng (Array)MapSet
Tốc độ tìm kiếmO(n)O(1)O(1)
Lưu cặp key-valueKhông tự nhiênCóKhông
Phần tử trùng lặpCho phépCho phép key trùng sẽ ghi đèKhông cho phép
Duyệt phần tửfor, forEachfor...of, forEachfor...of, forEach
Kiểu dữ liệu keyChỉ số nguyên (index)Bất kỳ (object, function,...)Chỉ giá trị

Từ đó tôi chuyển hẳn sang dùng Map cho các tác vụ cần tra cứu nhanh. Ví dụ, thay vì dùng mảng để quản lý điểm số học sinh, tôi dùng Map với key là tên học sinh.

Kết quả? Thời gian xử lý giảm từ vài giây xuống còn vài mili giây.

Nghe có vẻ "sốc" nhưng đó là sự thật. Tuy vậy, đừng vội bỏ mảng hoàn toàn nhé.

Mỗi công cụ đều có chỗ đứng riêng. Map và Set đặc biệt hữu ích khi bạn cần tốc độ hoặc tính duy nhất, nhưng mảng vẫn là lựa chọn tốt cho các thao tác tuần tự như sắp xếp hay lọc dữ liệu.

Hãy cân nhắc kỹ trước khi quyết định.

Cách dùng Map và Set trong thực tế Hỏi đáp nhanh như chớp

Bạn đã bao giờ tự hỏi: "Làm sao để tối ưu code JavaScript của mình với Map và Set?" Hãy để tôi giải đáp qua vài câu hỏi thường gặp từ các bạn mới học nhé. Hỏi: Tôi có một danh sách sản phẩm trong giỏ hàng, mỗi sản phẩm có id và số lượng.

Làm sao để cập nhật số lượng nhanh chóng? Đáp: Dùng Map là lý tưởng.

Bạn tạo Map với key là id sản phẩm, value là số lượng. Khi cần cập nhật, chỉ việc gọi map.set(id, newQuantity).

Nếu id chưa tồn tại, nó tự thêm mới. Nhanh gọn lẹ.

Hỏi: Tôi muốn loại bỏ các giá trị trùng lặp từ một mảng lớn, có cách nào đơn giản không? Đáp: Dùng Set!

Chỉ cần new Set(array) là xong. Set tự động loại bỏ mọi phần tử trùng.

Ví dụ: const unique = [...new Set([1,2,2,3,3,4])] sẽ cho bạn [1,2,3,4]. Cực kỳ tiện lợi.

Hỏi: Map và Set có thể dùng để lưu dữ liệu từ API không? Đáp: Có chứ.

Tôi từng dùng Map để cache dữ liệu từ API. Mỗi lần gọi API, tôi lưu kết quả vào Map với key là URL request.

Lần sau nếu cùng URL, tôi chỉ việc lấy từ Map, không cần gọi lại API. Giảm tải server và tăng tốc ứng dụng đáng kể.

Hỏi: Tôi có một mảng object người dùng, muốn nhóm theo tuổi. Map có giúp được không?

Đáp: Chắc chắn rồi. Dùng Map với key là tuổi, value là mảng người dùng.

Duyệt qua mảng, với mỗi người, lấy tuổi làm key, push vào mảng tương ứng. Kết quả là bạn có một bảng nhóm gọn gàng.

Dưới đây là bảng tổng hợp các phương thức thường dùng:

Phương thứcMapSet
Thêm phần tửmap.set(key, value)set.add(value)
Lấy phần tửmap.get(key)Không có (dùng has để kiểm tra)
Xóa phần tửmap.delete(key)set.delete(value)
Kiểm tra tồn tạimap.has(key)set.has(value)
Lấy kích thướcmap.sizeset.size
Xóa tất cảmap.clear()set.clear()

Một mẹo nhỏ từ kinh nghiệm cá nhân: khi debug, tôi thường dùng console.log([...map]) để xem toàn bộ nội dung Map dưới dạng mảng. Còn với Set, dùng console.log([...set]).

Dễ nhìn hơn nhiều so với cấu trúc gốc.

So sánh hiệu năng Map và Set "đấu" với mảng – ai thắng?

Để trả lời câu hỏi này, tôi đã tự tay chạy thử nghiệm trên máy tính cá nhân (CPU Intel Core i5, RAM 8GB, Node.js v18). Kết quả thu được khá thú vị.

Tôi tạo một mảng, một Map và một Set, mỗi cái chứa 100.000 phần tử. Sau đó đo thời gian thực hiện các thao tác cơ bản.

Dưới đây là bảng so sánh:

Thao tácMảng (ms)Map (ms)Set (ms)
Thêm 100.000 phần tử121514
Tìm kiếm phần tử cuối8.20.0030.002
Xóa phần tử ở giữa7.50.0010.001
Duyệt toàn bộ3.13.53.4

Nhìn vào bảng, bạn thấy gì? Ở thao tác thêm, cả ba gần như tương đương.

Nhưng khi tìm kiếm và xóa, Map và Set vượt trội hơn hẳn – nhanh hơn hàng nghìn lần. Cụ thể, tìm kiếm phần tử trong mảng mất 8.2 mili giây, trong khi Map chỉ mất 0.003 mili giây.

Con số này càng ấn tượng khi dữ liệu lớn hơn. Tuy vậy, có một điểm yếu: Map và Set tiêu tốn nhiều bộ nhớ hơn mảng.

Trong thử nghiệm, mảng 100.000 phần tử chiếm khoảng 2.8MB, trong khi Map chiếm 4.2MB và Set chiếm 3.9MB. Nếu bạn làm việc với dữ liệu siêu lớn (hàng triệu bản ghi), đây là yếu tố cần cân nhắc.

Một lưu ý khác: Map và Set không giúp các thao tác tuần tự như sort(), filter(), reduce() một cách tự nhiên. Bạn phải chuyển đổi sang mảng trước khi dùng.

Ví dụ: [...map].sort() hoặc [...set].filter(). Việc chuyển đổi này cũng tốn thời gian, nhưng thường không đáng kể.

Từ những số liệu trên, tôi rút ra kết luận: nếu ứng dụng của bạn cần tra cứu nhanh (ví dụ: kiểm tra trùng lặp, tìm kiếm theo ID), hãy dùng Map hoặc Set. Còn nếu bạn chủ yếu duyệt tuần tự và cần sắp xếp, mảng vẫn là lựa chọn an toàn.

Đừng quên kiểm tra kích thước dữ liệu thực tế để đưa ra quyết định phù hợp.

Kinh nghiệm thực tế Khi tôi dùng Map và Set cho dự án thương mại điện tử

Tôi từng tham gia xây dựng backend cho một website thương mại điện tử nhỏ. Dữ liệu sản phẩm lên tới 50.000 mặt hàng, mỗi sản phẩm có hàng chục thuộc tính như tên, giá, màu sắc, kích cỡ.

Ban đầu, team dùng mảng để quản lý. Kết quả?

Mỗi lần tìm kiếm sản phẩm theo ID mất tới 2-3 giây. Người dùng phàn nàn vì trang load chậm.

Tôi đề xuất chuyển sang Map. Cụ thể, tôi tạo một Map với key là ID sản phẩm (dạng string) và value là object chứa toàn bộ thông tin.

Sau đó, khi cần tìm sản phẩm, chỉ cần productMap.get(id). Thời gian tìm kiếm giảm từ 2-3 giây xuống còn 0.01 giây.

Cả team đều ngỡ ngàng. Không chỉ dừng lại ở đó, tôi còn dùng Set để quản lý danh sách mã giảm giá đã sử dụng.

Mỗi khi có đơn hàng, tôi thêm mã vào Set. Kiểm tra mã đã dùng chưa chỉ mất vài micro giây.

Trước đây, với mảng, việc này tốn tới vài trăm mili giây vì phải duyệt toàn bộ. Dưới đây là bảng so sánh trước và sau khi dùng Map/Set trong dự án:

Chức năngTrước (mảng)Sau (Map/Set)Cải thiện
Tìm sản phẩm theo ID2.5 giây0.01 giây250 lần
Kiểm tra mã giảm giá0.3 giây0.0001 giây3000 lần
Lọc sản phẩm theo danh mục1.8 giây0.5 giây3.6 lần
Cập nhật số lượng tồn kho0.8 giây0.02 giây40 lần

Tuy vậy, không phải lúc nào Map và Set cũng là "cứu cánh". Trong một module khác, tôi cần sắp xếp sản phẩm theo giá từ thấp đến cao.

Map không giúp sort(), nên tôi phải chuyển đổi sang mảng, sắp xếp, rồi chuyển lại. Việc này làm tăng thời gian xử lý thêm khoảng 20%.

Trong trường hợp này, giữ nguyên mảng có lẽ tốt hơn. Một lời khuyên từ kinh nghiệm: hãy ưu tiên dùng Map cho các tác vụ tra cứu thường xuyên (như kiểm tra tồn tại, lấy dữ liệu theo key), và Set cho các tác vụ cần tính duy nhất (như danh sách email đã gửi, ID phiên làm việc).

Còn nếu bạn cần sắp xếp, lọc, hoặc thống kê, mảng vẫn là lựa chọn an toàn hơn.

Lưu ý khi dùng Map và Set Tránh "bẫy" thường gặp

Dù Map và Set rất mạnh mẽ, nhưng không phải lúc nào cũng "dễ chịu". Tôi từng mắc vài lỗi ngớ ngẩn khi mới dùng.

Hãy cùng điểm qua những "bẫy" thường gặp để tránh nhé. Lỗi 1: Dùng object làm key trong Map
Tưởng tượng bạn dùng một object làm key: const key = {id: 1}; map.set(key, 'value');.

Khi truy cập lại bằng object khác có cùng nội dung: const anotherKey = {id: 1}; map.get(anotherKey), kết quả là undefined. Vì Map so sánh tham chiếu, không phải giá trị.

Giải pháp: dùng string hoặc number làm key. Lỗi 2: Set không loại bỏ object trùng lặp
Set loại bỏ các giá trị nguyên thủy trùng (số, string), nhưng với object, nó vẫn giữ nhiều bản sao nếu tham chiếu khác nhau.

Ví dụ: new Set([{a:1}, {a:1}]) sẽ có 2 phần tử. Để khắc phục, bạn có thể chuyển object thành string JSON hoặc dùng một thư viện như Lodash.

Lỗi 3: Quên rằng Map và Set không thể chuyển đổi trực tiếp sang JSON
Khi gọi JSON.stringify(map), bạn sẽ nhận được {}. Phải chuyển đổi thủ công: JSON.stringify([...map]) hoặc dùng Array.from(map).

Tương tự với Set. Lỗi 4: Sử dụng Map/Set với dữ liệu nhỏ
Nếu bạn chỉ có vài chục phần tử, việc dùng Map hay Set thay vì mảng là không cần thiết.

Overhead về bộ nhớ và cú pháp phức tạp hơn không đáng. Hãy dùng mảng cho các trường hợp đơn giản.

Dưới đây là bảng so sánh các lỗi và cách khắc phục:

LỗiMô tảGiải pháp
Key là objectSo sánh tham chiếu, không phải giá trịDùng string/number
Set với objectKhông loại bỏ object trùngChuyển object thành string
JSON không hỗ trợJSON.stringify trả về {}Chuyển Map/Set thành mảng trước
Overhead không cần thiếtTốn bộ nhớ và code phức tạpChỉ dùng khi dữ liệu lớn (>1000 phần tử)

Một mẹo nhỏ: tôi thường viết hàm wrapper để chuyển đổi Map/Set sang JSON một cách an toàn. Ví dụ:


function mapToJSON(map) {

  return JSON.stringify([...map]);

}

function jsonToMap(json) {

  return new Map(JSON.parse(json));

}

Cũng đừng quên kiểm tra trình duyệt giúp. Map và Set có trong ES6 (2015), hầu hết trình duyệt hiện đại đều giúp.

Nhưng nếu bạn phải giúp IE11, hãy dùng polyfill hoặc chuyển sang dùng object và mảng. Kết hợp các lưu ý này, bạn sẽ tránh được những "cú vấp" không đáng có khi làm việc với Map và Set.

Hãy nhớ: công cụ tốt nhưng dùng sai cách thì cũng vô ích.