メインコンテンツまでスキップ

Map<K, V>

MapはJavaScriptの組み込みAPIのひとつで、キーと値のペアを取り扱うためのオブジェクトです。Mapにはひとつのキーについてはひとつの値のみを格納できます。

Mapオブジェクトの作り方

Mapオブジェクトを作るにはMapクラスをnewします。たとえば、キーがstring、値がnumberMap<string, 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

コンストラクタにキーと値の[タプル型][K, V]の配列[K, V][]を渡すとMap<K, V>オブジェクトが作られます。

📄️ タプル

TypeScriptの関数は1値のみ返却可能です。戻り値に複数の値を返したい時に、配列に返したいすべての値を入れて返すことがあります。なお次の関数の戻り値は定数になっていますが、実際は演算した結果だと解釈してください。

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}

Mapの型変数を省略した場合、TypeScriptはコンストラクタ引数からMap<K, V>の型を推論します。

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>

コンストラクタ引数を省略した場合、空のMapが作られます。

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

型引数とコンストラクタ引数の両方を省略した場合、Map<any, any>型になります。

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

Mapの型注釈

TypeScriptでMapの型注釈をする場合は、Map<string, number>のようにMap要素の型を型変数に指定します。

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

Mapのキーは厳密等価で判定される

Mapのキーが同じかどうかは厳密等価(===)で判定します。等価(==)ではありません。

たとえば、nullundefinedは等価ですが、厳密等価では等しくありません。

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

そのため、Mapnullundefinedを異なるキーとみなします。

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同士は厳密等価ではありませんが、例外的に同じキーとみなされます。

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}

オブジェクトは等価でも厳密等価でもないため、別のキーとみなされます。

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}

Mapの操作

要素をセットする - Map.prototype.set()

Mapにキーと値のペアを追加するには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}

すでにキーがある場合は、値を上書きします。

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}

値を取得する - Map.prototype.get()

Mapからキーをもとに要素を取得するには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

getメソッドは、キーが存在しない場合、undefinedを返します。

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

Null合体演算子と組み合わせることによって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

特定の要素を削除する - Map.prototype.delete()

Mapからキーを指定して要素を削除するには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}

deleteの戻り値は、キーが存在した場合true、そうでない場合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

キーの有無を確認する - Map.prototype.has()

Mapにキーが存在するかどうかを調べるには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
存在確認からの要素取得

要素が存在するかをhasチェックしてから、getで要素を取得するコードはTypeScriptではうまく書けません。

ts
const map = new Map([["a", 1]]);
if (map.has("a")) {
// TypeScriptは"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は"a"があることを認識しない
const n = map.get("a");
n * 2;
'n' is possibly 'undefined'.18048'n' is possibly 'undefined'.
}

この場合、getで値を取得して、その値がundefinedでないことをチェックするとうまくいきます。

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;
}

要素の個数を取得する - Map.prototype.size()

Mapに登録されている要素数を調べるには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

全要素を削除する - Map.prototype.clear()

Mapに登録されている要素をすべて削除するには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

キーを列挙する - Map.prototype.keys()

keysメソッドはキーの反復可能オブジェクトを返します。

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"]

値を列挙する - Map.prototype.values()

valuesメソッドは値の反復可能オブジェクトを返します。

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]

キーと値のペアを列挙する - Map.prototype.entries()

entriesメソッドはキーと値の反復可能オブジェクトを返します。

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]]

キーと値のペアを反復する

Mapfor...ofで反復できます。反復の順序は登録された順です。

ts
const map = new Map([
["a", 1],
["b", 2],
["c", 3],
]);
 
for (const [key, value] of map) {
console.log(key, value);
// "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);
// "a", 1
// "b", 2
// "c", 3 の順で出力される
}

複製する

Mapオブジェクトを複製(シャローコピー)するには、Mapオブジェクトを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は直接JSONにできない

MapオブジェクトはJSON.stringifyにかけても、登録されている要素は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));
"{}"

MapをJSON化する場合は、一度オブジェクトにする必要があります。

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}"

他の型との相互運用

Mapを配列にする

Map<K, V>にスプレッド構文を使うと、タプル型配列[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]]

オブジェクトをMapにする

オブジェクトをMapに変換するには、Object.entriesの戻り値を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}

Mapをオブジェクトにする

Mapをオブジェクトにするには、Object.fromEntriesにMapオブジェクトを渡します。

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 }

Mapとオブジェクトの違い

キーと値のペアが表現ができるという点で、Mapとオブジェクトは似ていますが、次の違いがあります。

違いMapオブジェクト
プロトタイプキーの上書き起きない起こりうる
キーに使える型任意の型stringsymbol
反復の順序挿入順複雑なロジック
JSON化直接できない直接できる

プロトタイプキーの上書き

オブジェクトはプロトタイプのキーを上書きする可能性があります。

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は要素をセットしてもプロトタイプのキーを上書きする心配がありません。要素とプロトタイプは別の領域にあるためです。

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] }

キーに使える型

オブジェクトのキーに使える型はstring型かsymbol型のどちらです。Mapは任意の型をキーにできます。

反復の順序

オブジェクトのプロパティの反復順序は、書いた順や追加した順ではなく、複雑なロジックになっています。

📄️ オブジェクトをループする方法

JavaScript・TypeScriptでオブジェクトのプロパティをループする方法を説明します。

Mapの要素の反復順序は要素を追加した順であることが保証されています。

JSON化

オブジェクトはそのままJSON.stringifyでJSON化できます。MapJSON.stringifyしても要素はJSONになりません。一度Mapをオブジェクトに変換する必要があります。

Mapとオブジェクトの書き方比較

Mapとオブジェクトは似た操作ができます。次がその対応表です。

Mapオブジェクト
型注釈の書き方Map<K, V>Record<K, V>
初期化new Map([["a", 1]]){ a: 1 }
要素のセットmap.set(key, value)obj[key] = value
値の取得map.get(key)obj[key]
要素の削除map.delete(key)delete obj.key
キーの有無確認map.has(key)key in obj
要素数の取得map.sizeObject.keys(obj).length
全要素削除map.clear()-
キーの列挙map.keys()Object.keys(obj)
値の列挙map.values()Object.values(obj)
要素の列挙map.entries()Object.entries(obj)
複製new Map(map){ ...obj }

📄️ Record<Keys, Type>

キー・バリューからオブジェクト型を作る

学びをシェアする

🗺Mapはキーと値のペアを扱うJSビルトインのAPI
📝TypeScriptではMap<string, number>のように型注釈する
🔬キーは厳密等価で判定される
🔪Mapは直接JSON化できない

⚖️Mapとオブジェクトの違い
→ Mapはキーに任意の型が使える
→ Mapはキーの順序が挿入順保証

『サバイバルTypeScript』より

この内容をツイートする