Nhảy tới nội dung

Map<K, V>

Map là một trong những built-in API của JavaScript, là object để xử lý cặp key-value. Trong Map, một key chỉ có thể lưu trữ một giá trị duy nhất.

Cách tạo đối tượng Map

Để tạo đối tượng Map, dùng new với class Map. Ví dụ, để tạo Map<string, number> với key là string và value là number:

ts
const map = new Map<string, number>();
map.set("a", 1);
console.log(map.get("a"));
1
ts
const map = new Map<string, number>();
map.set("a", 1);
console.log(map.get("a"));
1

Khi truyền mảng [tuple type] [K, V][][K, V][] vào constructor, sẽ tạo object Map<K, V>.

📄️ Tuple

Function của TypeScript chỉ có thể trả về 1 giá trị. Tuy nhiên, thực tế có lúc muốn trả về nhiều giá trị. Trong trường hợp đó, có thể đặt tất cả giá trị muốn trả về vào mảng và return.

ts
const map = new Map<string, number>([
["a", 1],
["b", 2],
["c", 3],
]);
console.log(map);
Map (3) {"a" => 1, "b" => 2, "c" => 3}
ts
const map = new Map<string, number>([
["a", 1],
["b", 2],
["c", 3],
]);
console.log(map);
Map (3) {"a" => 1, "b" => 2, "c" => 3}

Nếu bỏ qua type variable của Map, TypeScript sẽ suy luận kiểu Map<K, V> từ tham số constructor.

ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
map;
const map: Map<string, number>
ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
map;
const map: Map<string, number>

Nếu bỏ qua tham số constructor, sẽ tạo Map rỗng.

ts
const map = new Map<string, number>();
console.log(map);
Map(0) {}
ts
const map = new Map<string, number>();
console.log(map);
Map(0) {}

Nếu bỏ qua cả type argument và constructor argument, sẽ có kiểu Map<any, any>.

ts
const map = new Map();
const map: Map<any, any>
ts
const map = new Map();
const map: Map<any, any>

Type annotation cho Map

Khi type annotation cho Map trong TypeScript, chỉ định kiểu của phần tử Map vào type variable như Map<string, number>.

ts
function doSomething(map: Map<string, number>) {}
ts
function doSomething(map: Map<string, number>) {}

Key của Map được so sánh bằng strict equality

Việc key của Map có giống nhau hay không được xác định bằng strict equality (===). Không phải equality (==).

Ví dụ, nullundefined bằng nhau với equality, nhưng không bằng nhau với strict equality.

ts
console.log(null == undefined);
true
console.log(null === undefined);
false
ts
console.log(null == undefined);
true
console.log(null === undefined);
false

Do đó, Map coi nullundefined là các key khác nhau.

ts
const map = new Map<any, any>([[null, 1]]);
console.log(map.has(null));
true
console.log(map.has(undefined));
false
ts
const map = new Map<any, any>([[null, 1]]);
console.log(map.has(null));
true
console.log(map.has(undefined));
false

NaN với nhau không bằng nhau với strict equality, nhưng ngoại lệ được coi là cùng key.

js
// JavaScript
 
console.log(NaN === NaN);
false
js
// JavaScript
 
console.log(NaN === NaN);
false
ts
const map = new Map<number, number>();
map.set(NaN, 1);
map.set(NaN, 2);
console.log(map);
Map (1) {NaN => 2}
ts
const map = new Map<number, number>();
map.set(NaN, 1);
map.set(NaN, 2);
console.log(map);
Map (1) {NaN => 2}

Object không bằng nhau với cả equality và strict equality nên được coi là key khác nhau.

js
// JavaScript
 
console.log({} == {});
false
console.log({} === {});
false
js
// JavaScript
 
console.log({} == {});
false
console.log({} === {});
false
ts
const map = new Map<object, number>();
map.set({}, 1);
map.set({}, 2);
console.log(map);
Map (2) {{} => 1, {} => 2}
ts
const map = new Map<object, number>();
map.set({}, 1);
map.set({}, 2);
console.log(map);
Map (2) {{} => 1, {} => 2}

Thao tác với Map

Set phần tử - Map.prototype.set()

Để thêm cặp key-value vào Map, sử dụng method set.

ts
const map = new Map<string, number>();
map.set("a", 1);
console.log(map);
Map (1) {"a" => 1}
ts
const map = new Map<string, number>();
map.set("a", 1);
console.log(map);
Map (1) {"a" => 1}

Nếu key đã tồn tại, sẽ ghi đè giá trị.

ts
const map = new Map([["a", 1]]);
map.set("a", 5);
console.log(map);
Map (1) {"a" => 5}
ts
const map = new Map([["a", 1]]);
map.set("a", 5);
console.log(map);
Map (1) {"a" => 5}

Lấy giá trị - Map.prototype.get()

Để lấy phần tử từ Map dựa trên key, sử dụng method get.

ts
const map = new Map([["a", 1]]);
console.log(map.get("a"));
1
ts
const map = new Map([["a", 1]]);
console.log(map.get("a"));
1

Method get trả về undefined nếu key không tồn tại.

ts
const map = new Map([["a", 1]]);
console.log(map.get("b"));
undefined
ts
const map = new Map([["a", 1]]);
console.log(map.get("b"));
undefined

Kết hợp với toán tử Null coalescing có thể gán giá trị mặc định khi không lấy được giá trị bằng method get.

ts
const map = new Map([["a", 1]]);
console.log(map.get("b") ?? 2);
2
ts
const map = new Map([["a", 1]]);
console.log(map.get("b") ?? 2);
2

Xóa phần tử cụ thể - Map.prototype.delete()

Để xóa phần tử khỏi Map bằng cách chỉ định key, sử dụng method delete.

ts
const map = new Map([
["a", 1],
["b", 2],
]);
map.delete("a");
console.log(map);
Map (1) {"b" => 2}
ts
const map = new Map([
["a", 1],
["b", 2],
]);
map.delete("a");
console.log(map);
Map (1) {"b" => 2}

Giá trị trả về của deletetrue nếu key tồn tại, ngược lại là false.

ts
const map = new Map([["a", 1]]);
console.log(map.delete("a"));
true
console.log(map.delete("b"));
false
ts
const map = new Map([["a", 1]]);
console.log(map.delete("a"));
true
console.log(map.delete("b"));
false

Kiểm tra sự tồn tại của key - Map.prototype.has()

Để kiểm tra xem key có tồn tại trong Map hay không, sử dụng method has.

ts
const map = new Map([["a", 1]]);
console.log(map.has("a"));
true
console.log(map.has("b"));
false
ts
const map = new Map([["a", 1]]);
console.log(map.has("a"));
true
console.log(map.has("b"));
false
Lấy phần tử sau khi kiểm tra tồn tại

Code kiểm tra tồn tại bằng has rồi mới lấy phần tử bằng get không thể viết tốt trong TypeScript.

ts
const map = new Map([["a", 1]]);
if (map.has("a")) {
// TypeScript không nhận biết có "a"
const n = map.get("a");
n * 2;
'n' is possibly 'undefined'.18048'n' is possibly 'undefined'.
}
ts
const map = new Map([["a", 1]]);
if (map.has("a")) {
// TypeScript không nhận biết có "a"
const n = map.get("a");
n * 2;
'n' is possibly 'undefined'.18048'n' is possibly 'undefined'.
}

Trong trường hợp này, lấy giá trị bằng get rồi kiểm tra giá trị đó khác undefined sẽ hoạt động tốt.

ts
const map = new Map([["a", 1]]);
const n = map.get("a");
if (typeof n === "number") {
n * 2;
}
ts
const map = new Map([["a", 1]]);
const n = map.get("a");
if (typeof n === "number") {
n * 2;
}

Lấy số lượng phần tử - Map.prototype.size()

Để kiểm tra số lượng phần tử đã đăng ký trong Map, xem giá trị của field size.

ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
console.log(map.size);
3
ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
console.log(map.size);
3

Xóa tất cả phần tử - Map.prototype.clear()

Để xóa tất cả phần tử đã đăng ký trong Map, sử dụng method clear.

ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
console.log(map.size);
3
map.clear();
console.log(map.size);
0
ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
console.log(map.size);
3
map.clear();
console.log(map.size);
0

Liệt kê key - Map.prototype.keys()

Method keys trả về iterable object của các key.

ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
const keys = [...map.keys()];
console.log(keys);
["a", "b", "c"]
ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
const keys = [...map.keys()];
console.log(keys);
["a", "b", "c"]

Liệt kê value - Map.prototype.values()

Method values trả về iterable object của các giá trị.

ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
const values = [...map.values()];
console.log(values);
[1, 2, 3]
ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
const values = [...map.values()];
console.log(values);
[1, 2, 3]

Liệt kê cặp key-value - Map.prototype.entries()

Method entries trả về iterable object của key và value.

ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
const keyValues = [...map.entries()];
console.log(keyValues);
[["a", 1], ["b", 2], ["c", 3]]
ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
const keyValues = [...map.entries()];
console.log(keyValues);
[["a", 1], ["b", 2], ["c", 3]]

Lặp qua các cặp key-value

Map có thể lặp bằng for...of. Thứ tự lặp là theo thứ tự đã đăng ký.

ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
 
for (const [key, value] of map) {
console.log(key, value);
// Output theo thứ tự "a", 1
// "b", 2
// "c", 3
}
ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
 
for (const [key, value] of map) {
console.log(key, value);
// Output theo thứ tự "a", 1
// "b", 2
// "c", 3
}

Sao chép

Để sao chép (shallow copy) object Map, truyền object Map vào constructor Map.

ts
const map1 = new Map([["a", 1]]);
const map2 = new Map(map1);
console.log(map2);
Map (1) {"a" => 1}
ts
const map1 = new Map([["a", 1]]);
const map2 = new Map(map1);
console.log(map2);
Map (1) {"a" => 1}

Map không thể chuyển trực tiếp thành JSON

Khi đưa object Map qua JSON.stringify, các phần tử đã đăng ký sẽ không trở thành JSON.

ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
console.log(JSON.stringify(map));
"{}"
ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
console.log(JSON.stringify(map));
"{}"

Khi muốn chuyển Map thành JSON, cần chuyển thành object trước.

ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
const obj = Object.fromEntries(map);
console.log(JSON.stringify(obj));
"{"a":1,"b":2,"c":3}"
ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
const obj = Object.fromEntries(map);
console.log(JSON.stringify(obj));
"{"a":1,"b":2,"c":3}"

Tương tác với các kiểu khác

Chuyển Map thành mảng

Sử dụng spread syntax với Map<K, V> sẽ được mảng tuple type [K, V][].

ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
const keyValues = [...map];
console.log(keyValues);
[["a", 1], ["b", 2], ["c", 3]]
ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
const keyValues = [...map];
console.log(keyValues);
[["a", 1], ["b", 2], ["c", 3]]

Chuyển object thành Map

Để chuyển object thành Map, truyền giá trị trả về của Object.entries vào constructor Map.

ts
const obj = { a: 1, b: 2, c: 3 };
const map = new Map(Object.entries(obj));
console.log(map);
Map (3) {"a" => 1, "b" => 2, "c" => 3}
ts
const obj = { a: 1, b: 2, c: 3 };
const map = new Map(Object.entries(obj));
console.log(map);
Map (3) {"a" => 1, "b" => 2, "c" => 3}

Chuyển Map thành object

Để chuyển Map thành object, truyền object Map vào Object.fromEntries.

ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
const obj = Object.fromEntries(map);
console.log(obj);
{ "a": 1, "b": 2, "c": 3 }
ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
const obj = Object.fromEntries(map);
console.log(obj);
{ "a": 1, "b": 2, "c": 3 }

Sự khác biệt giữa Map và object

Về điểm có thể biểu diễn cặp key-value, Map và object tương tự nhau, nhưng có các điểm khác biệt sau:

Điểm khác biệtMapObject
Ghi đè prototype keyKhông xảy raCó thể xảy ra
Kiểu có thể dùng làm keyBất kỳ kiểu nàostring hoặc symbol
Thứ tự lặpTheo thứ tự chènLogic phức tạp
Chuyển thành JSONKhông thể trực tiếpTrực tiếp được

Ghi đè prototype key

Object có thể ghi đè key của prototype.

js
const obj = {};
console.log(obj.toString);
function toString() { [native code] }
obj.toString = 1;
console.log(obj.toString);
1
js
const obj = {};
console.log(obj.toString);
function toString() { [native code] }
obj.toString = 1;
console.log(obj.toString);
1

Map không lo ngại ghi đè key của prototype khi set phần tử. Bởi vì phần tử và prototype ở các vùng riêng biệt.

ts
const map = new Map<string, any>();
console.log(map.toString);
function toString() { [native code] }
map.set("toString", 1);
console.log(map.toString);
function toString() { [native code] }
ts
const map = new Map<string, any>();
console.log(map.toString);
function toString() { [native code] }
map.set("toString", 1);
console.log(map.toString);
function toString() { [native code] }

Kiểu có thể dùng làm key

Kiểu có thể dùng làm key của object là kiểu string hoặc kiểu symbol. Map có thể dùng bất kỳ kiểu nào làm key.

Thứ tự lặp

Thứ tự lặp qua property của object không phải theo thứ tự viết hoặc thêm vào, mà theo logic phức tạp.

📄️ Cách loop object

Giải thích cách loop qua property của object trong JavaScript/TypeScript.

Thứ tự lặp qua phần tử của Map được đảm bảo theo thứ tự thêm phần tử.

Chuyển thành JSON

Object có thể chuyển trực tiếp thành JSON bằng JSON.stringify. Map khi JSON.stringify thì phần tử không trở thành JSON. Cần chuyển Map thành object trước.

So sánh cách viết Map và object

Map và object có thể thực hiện các thao tác tương tự. Dưới đây là bảng tương ứng:

MapObject
Cách viết type annotationMap<K, V>Record<K, V>
Khởi tạonew Map([["a", 1]]){ a: 1 }
Set phần tửmap.set(key, value)obj[key] = value
Lấy giá trịmap.get(key)obj[key]
Xóa phần tửmap.delete(key)delete obj.key
Kiểm tra keymap.has(key)key in obj
Lấy số lượng phần tửmap.sizeObject.keys(obj).length
Xóa tất cả phần tửmap.clear()-
Liệt kê keymap.keys()Object.keys(obj)
Liệt kê valuemap.values()Object.values(obj)
Liệt kê phần tửmap.entries()Object.entries(obj)
Sao chépnew Map(map){ ...obj }

📄️ Record<Keys, Type>

Tạo object type từ key-value

Chia sẻ kiến thức

Map là built-in API của JS để xử lý cặp key-value
Type annotation trong TypeScript: Map<string, number>
Key được so sánh bằng strict equality
Map không thể chuyển trực tiếp thành JSON

Sự khác biệt giữa Map và object
→ Map có thể dùng bất kỳ kiểu nào làm key
→ Map đảm bảo thứ tự key theo thứ tự chèn

Từ 『Survival TypeScript』

Đăng nội dung này lên X