Học bằng cách đọc theo thứ tự

Map và Set — Bỏ Trùng Lặp và Đếm Theo Key

Học Set giữ mỗi giá trị một lần và Map giữ cặp key-value theo thứ tự: bỏ trùng, thêm xóa, tra theo key.

Gom các tag gắn trên một sản phẩm vào một mảng, cùng một tag cứ lặp đi lặp lại — muốn biết thực sự có bao nhiêu loại khác nhau thì phải kiểm tra bằng includes mỗi lần. Muốn giữ một cặp key-value, như số lượng tồn kho theo mã sản phẩm, cũng gặp đúng vấn đề đó nếu chỉ dùng mảng: bạn sẽ cần hai mảng riêng cho ID và số lượng, khớp nhau theo vị trí, và key của object chỉ giới hạn ở chuỗi và symbol, nên không thể dùng số làm key nguyên vẹn.

Bài này nói về Set, không bao giờ giữ trùng lặp, và Map, có thể dùng cả những thứ khác ngoài chuỗi làm key.

Bỏ Trùng Lặp — Xây Một Set

Giả sử bạn muốn biết có bao nhiêu tag khác nhau thực sự đang được dùng trong một danh sách sản phẩm có gắn tag. Trong một mảng thường, cùng một tag xuất hiện nhiều lần, nên đếm nguyên trạng sẽ làm con số bị thổi phồng. Bạn cần một cấu trúc dữ liệu giữ sẵn các giá trị đã bỏ trùng lặp.

Một Set (cấu trúc dữ liệu giữ mỗi giá trị chỉ một lần) được xây bằng cách truyền vào một mảng, như new Set(tags). new ở đầu là cú pháp bạn đặt trước khi tạo một instance mới của một loại cấu trúc dữ liệu cố định, như Set hay Map.

Vì cùng một giá trị không bao giờ thêm trùng lặp, chỉ cần đọc size là biết số lượng giá trị khác nhau. Hai giá trị có tính là giống nhau hay không theo cùng quy tắc như ===.

Một Set không phải mảng, nên index như uniqueTags[0] sẽ không đọc được gì. Khi cần nó dưới dạng mảng, hãy chuyển đổi bằng spread syntax, như [...uniqueTags].

Thứ tự giữ đúng như lúc các mục lần đầu được thêm vào, nên thứ tự xuất hiện lần đầu của mảng gốc được giữ nguyên không đổi.

Một Set Gộp Các Giá Trị Trùng Lặp Thành Một
Lần 1SaleChưaVào làmmục thứ 1Lần 2NewChưaVào làmmục thứ 2Lần 3SaleĐãSố lượngkhông tăng
Không có gì xảy ra khi một giá trị nó đã có đi vào. Thứ tự các mục được thêm vào giữ đúng như nó vốn có.
const tags = ["Sale", "New", "Sale", "Free Shipping"];

// Truyền một mảng vào, và bạn nhận một Set đã bỏ trùng lặp
const uniqueTags = new Set(tags);
console.log(uniqueTags.size);              // 3

// Chuyển lại thành mảng bằng spread syntax (giữ nguyên thứ tự thêm vào)
console.log([...uniqueTags].join(", "));   // Sale, New, Free Shipping

// Mảng gốc không đụng tới
console.log(tags.length);                  // 4

// Bạn cũng có thể bắt đầu từ một Set rỗng
const empty = new Set();
console.log(empty.size);                   // 0

Lấy ra các tag khác nhau thực sự đang dùng, từ một mảng tag gắn trên sản phẩm. tags đã được khai báo sẵn.

① Hiển thị số lượng của mảng gốc.

② Xây một cấu trúc dữ liệu đã bỏ trùng lặp, và hiển thị số lượng của nó.

③ Chuyển kết quả đã bỏ trùng lặp lại thành mảng, ghép bằng ", ", và hiển thị.

④ Hiển thị mục thứ 2 của mảng ở ③, xác nhận thứ tự đã được giữ nguyên.

(Nếu chạy đúng, phần giải thích sẽ xuất hiện.)

JavaScript / TypeScript Editor

Chạy code để xem đầu ra

Thêm và Xóa Giá Trị — add, has, và delete

Đôi khi bạn muốn ghi lại ID người truy cập và đếm cùng một người là 1 dù họ quay lại bao nhiêu lần. Giữ điều này trong một mảng, và mỗi lần thêm nghĩa là kiểm tra bằng includes trước push — càng nhiều bản ghi chồng lên, việc kiểm tra càng mất nhiều thời gian.

Một Set có add để thêm một giá trị, has để kiểm tra nó có tồn tại không, delete để xóa một cái, và size cho số lượng. add không làm gì khi cùng giá trị đó đi vào, nên bạn không bao giờ cần kiểm tra trước khi thêm.

has trả về true hoặc false, và delete trả về nó có thực sự xóa được gì không.

size Di Chuyển Thế Nào Với add và delete
add 1 ID mớiChưasize tăngthêm 1add cùngID đóĐãsizegiữ nguyêndelete1 IDXóa nó khỏibản ghisize giảmđi 1
add cùng một giá trị bao nhiêu lần cũng không bao giờ làm tăng số lượng. Chỉ xóa mới làm giảm nó.
const visitors = new Set(["u-102", "u-337"]);

// Kiểm tra nó có tồn tại không
console.log(visitors.has("u-102"));   // true
console.log(visitors.has("u-999"));   // false

// Thêm một cái. Cùng giá trị không đổi số lượng
visitors.add("u-541");
console.log(visitors.size);           // 3
visitors.add("u-541");
console.log(visitors.size);           // 3

// Xóa một cái. Nó có thực sự xóa được gì không sẽ trả về
console.log(visitors.delete("u-102"));  // true
console.log(visitors.delete("u-102"));  // false (đã mất rồi)
console.log(visitors.size);             // 2

Thao tác trên một bản ghi khách truy cập hôm nay và xác nhận số lượng thay đổi thế nào. visitors đã được khai báo sẵn.

① Hiển thị u-337 đã được ghi lại chưa.

② Ghi lại u-999, và hiển thị số lượng.

③ Ghi lại cùng u-999 đó lần nữa, và hiển thị số lượng.

④ Xóa u-102 khỏi bản ghi, và hiển thị nó còn đó không.

JavaScript / TypeScript Editor

Chạy code để xem đầu ra

Giữ Cặp Key-Value — Map

Đôi khi bạn muốn giữ một key và value thành một cặp, như số lượng tồn kho theo mã sản phẩm. Object cũng làm được việc này, nhưng để đếm các key được quyết định lúc chạy — như ID — và điều chỉnh chúng theo thời gian, một cấu trúc dữ liệu có phương thức chuyên dụng sẽ phù hợp hơn.

Một Map (cấu trúc dữ liệu giữ cặp key-value theo thứ tự chúng được đăng ký) được xây bằng new Map(), và bạn đăng ký các mục bằng set(key, value). Để đọc một cái, dùng get(key); để kiểm tra một cái có tồn tại không, has(key); cho số lượng, size.

set cùng key lần nữa, và giá trị bị ghi đè. Để bắt đầu với các mục đã điền sẵn, truyền vào một mảng các cặp, như new Map([["P-001", 12], ["P-002", 5]]).

Một Map Đọc và Ghi Cặp Theo Key
set P-001và 12Đăng kýnhư một cặpsize tăngthêm 1get,cho P-001Tra cứukeyTrả vềgiá trị, 12get,cho P-999Khôngtìm thấyTrả vềundefined
set đăng ký một cặp, và get tra cứu một giá trị theo key. Một key không có mục nào trả về undefined.
const stock = new Map();

// Đăng ký một cặp bằng set
stock.set("P-001", 12);
stock.set("P-002", 5);
console.log(stock.size);            // 2

// Tra cứu một giá trị từ key bằng get
console.log(stock.get("P-002"));    // 5
console.log(stock.get("P-999"));    // undefined
console.log(stock.has("P-999"));    // false

// set cùng key lần nữa, và nó ghi đè
stock.set("P-001", 8);
console.log(stock.get("P-001"));    // 8
console.log(stock.size);            // 2 (số lượng không tăng)

// Bạn cũng có thể xây với các cặp đã điền sẵn
const prices = new Map([["P-001", 3980], ["P-002", 2480]]);
console.log(prices.get("P-001"));   // 3980

Không Thể Đọc Giá Trị Của Map Bằng Dot Notation

Viết stock["P-001"] thay vì stock.get("P-001"), và bạn sẽ không nhận lại giá trị đó. Cú pháp đó tìm một property trên chính object Map, không liên quan gì đến các cặp bạn đã đăng ký, nên nó trả về undefined. Định tuyến việc đọc và ghi qua getset.

Xây một cấu trúc dữ liệu giữ số lượng tồn kho theo mã sản phẩm. stock đã được khai báo sẵn, rỗng.

① Đăng ký P-001 là 12, P-002 là 5, và P-003 là 3, rồi hiển thị số lượng.

② Hiển thị số lượng tồn kho của P-002.

③ Hiển thị số lượng tồn kho của P-999 chưa đăng ký.

④ Cập nhật tồn kho của P-001 thành 4, và hiển thị giá trị sau khi cập nhật.

JavaScript / TypeScript Editor

Chạy code để xem đầu ra

Chọn Giữa Map và Object — Kiểu Key và Thứ Tự

Vì nó giữ key và value giống hệt như một object, bạn phải chọn giữa chúng. Khi key cố định tại thời điểm bạn viết code, như các trường cài đặt, một object là đủ; khi bạn đếm các key tăng lên lúc chạy, như ID hay ngày tháng, Map dễ làm việc hơn.

Kiểu key và thứ tự là điều bạn dựa vào để quyết định.

Key của một object bị chuyển thành chuỗi, nên ngay cả viết record[2026], key thực sự là "2026". Một Map dùng bất kỳ giá trị nào bạn truyền làm key đúng như nó vốn có, nên số 2026 và chuỗi "2026" được coi là các key khác nhau.

Thứ tự cũng hoạt động khác — một Map luôn giữ thứ tự các mục được đăng ký.

Một Map Giữ Kiểu Của Key Đúng Như Nó Vốn Có
Đăng ký vớiMap dùng 2026Giữ nguyênlà key sốget(2026)lấy lại đượcTra cùng Mapbằng chuỗiCoi là keykhác nhauTrả vềundefinedĐăng ký vớiObject dùng 2026Chuyển thànhchuỗiChuỗi cũnglấy lại được
Một Map coi số 2026 và chuỗi "2026" là các key khác nhau. Một object chuyển cả hai thành chuỗi.
Khía CạnhObjectMap
Kiểu được phép làm keyChuyển thành chuỗiBất kỳ kiểu nào, giữ nguyên
Thứ tựKey dạng số nguyên xếp trướcGiữ theo thứ tự đăng ký
Đếm số mụclength của Object.keyssize
Đọc & ghiobj.key / obj[key]get và set
Phù hợp nhất choTập hợp trường cố địnhĐếm key tăng lên lúc chạy

Để liệt kê nội dung của một Map, viết [...stock.entries()] để chuyển nó thành mảng các cặp [key, value], mỗi cặp là một mảng 2 phần tử. Điều đó cho phép bạn lấy ra từng cặp một, như entries[0]. Lặp lại cùng một bước cho từng cặp, dù có bao nhiêu cặp, sẽ được nói lại ở bài về vòng lặp.

Khi bạn chỉ cần key, dùng stock.keys() — nhưng vì cái trả về không phải mảng, bạn không thể nối join trực tiếp vào nó. Viết [...stock.keys()] để chuyển nó thành mảng trước.

const scores = new Map([["math", 80]]);

// Một Map giữ key số nguyên vẹn dạng số
scores.set(2026, "tổng kết năm nay");
console.log(scores.get(2026));               // tổng kết năm nay
console.log(scores.get("2026"));             // undefined (key khác nhau)

// Key của một object bị chuyển thành chuỗi
const record = {};
record[2026] = "tổng kết năm nay";
console.log(Object.keys(record)[0]);         // 2026
console.log(typeof Object.keys(record)[0]);  // string
console.log(record["2026"]);                 // tổng kết năm nay (coi là cùng key)

// Chuyển keys() / entries() thành mảng bằng spread syntax trước khi dùng
console.log([...scores.keys()].join(", "));   // math, 2026

const pairs = [...scores.entries()];
console.log(pairs[0].join(": "));             // math: 80

// Một object xếp key dạng số nguyên trước
const ranking = {};
ranking[3] = "c";
ranking[1] = "a";
console.log(Object.keys(ranking).join(", "));  // 1, 3

Liệt kê nội dung một Map tồn kho, rồi xác nhận sự khác biệt do kiểu key tạo ra. stock đã được khai báo sẵn, với 3 cặp đã đăng ký.

① Biến chỉ các key đã đăng ký thành mảng, và hiển thị ghép bằng ", ".

② Chuyển 3 cặp thành một mảng các cặp [key, value], và hiển thị mỗi cặp định dạng "key: value" qua 3 dòng.

③ Đăng ký "Annual Ranking" với key là số 2026, và lấy lại bằng đúng key đó để hiển thị.

④ Lấy lại cùng mục đó bằng chuỗi "2026" thay vào đó, và hiển thị kết quả.

JavaScript / TypeScript Editor

Chạy code để xem đầu ra
QUIZ

Kiểm tra kiến thức

Hãy trả lời từng câu hỏi một.

Câu 1Với const ids = new Set(["u-1", "u-2"]);, chạy ids.add("u-1");. ids.size là gì?

Câu 2Với stock là một Map, stock.get("P-999") trả về gì cho một key chưa đăng ký?

Câu 3Đăng ký số 2026 làm key. Khác biệt giữa Map và một object là gì?