🐥

【JavaScript / TypeScript】Mapについてまとめてみる

2025/02/05に公開
2

はじめに

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

https://typescriptbook.jp/reference/builtin-api/map

上記Mapについて、筆者はインプットしただけで全然使ったことがありませんでした。
つい最近はまっていた処理をMapによってスマートに解決できたので、自身の戒め備忘録としてまとめていきたいと思います。

Mapの基本概念

JavaScript(TypeScript)Mapは、key(property)valueのペアを保持するコレクション(Pythonでいえばdict(辞書)に近い概念)だそうです。

key(property)valueのペアと言うとオブジェクトを想起しそうですが、通常のオブジェクトとは違って任意の型(オブジェクト、関数なども)をkey(property)に使用できます。

また、key(property)の順序が挿入順で保持されるのも特徴です。

// 新しいMap(空)の作成
const createMap: Map<any, any> = new Map();

// 新しいMap(文字列)の作成
const map_str_str: Map<string, string> = new Map([
    ['key1', 'value1']
]);

// 新しいMap(文字列と数値)の作成
const map_str_number: Map<string, number> = new Map([
    ['key1', 100]
]);

const hello: () => void = () => {
    console.log('hello');
}
// 新しいMap(文字列と関数)の作成
const map_hello: Map<string, () => void> = new Map([
    ['key1', hello]
]);

主要なメソッド

  • set
    値のセット
// 単一の値をセット
createMap.set('key1', 'value1');
  • get
    値の取得
// 'key1'の値:'value1'
console.log(createMap.get('key1'));
// 存在しないキー・プロパティの場合:undefined
console.log(createMap.get('noKey'));
  • has
    キー・プロパティの存在確認
// true
console.log(createMap.has('key1'));
// false
console.log(createMap.has('キー'));
  • delete
    値の削除
createMap.delete('key1');
  • size
    サイズ(配列でいうlength)の取得
// 配列でいう length
console.log(createMap.size);

// しかし Map オブジェクトでは length で処理不可能なので注意
console.log(createMap.length); // ← これ無理
  • clear
    全ての値をクリア(配列でいうsplice(0)
createMap.clear();

// 配列でいう splice(0)
// const lists: string[] = ["beer", "wine", "whisky", "water", "soda"];
// lists.splice(0);

通常のオブジェクトとの違いや、イテレーション(反復処理)について

通常のオブジェクトとは違って任意の型(オブジェクト、関数なども)をkey(property)に使用できます。と冒頭で説明した通り、Mapでは以下のような形式でもkey(property)valueを用意できます。

const fruitMap: Map<string, number> = new Map([
    ['apple', 5],
    ['banana', 3],
    ['orange', 2]
]);
  • イテレーション(反復処理)について
// キーと値の両方を取得(引数順序は`(value, key)`でなければならない)
// ※ Map.forEach() のコールバック関数は仕様により、
// 引数の順序が(value, key)となっているため 
fruitMap.forEach((value, key) => {
    console.log(`${key}: ${value}`);
});

// キーのみを取得
for (const key of fruitMap.keys()) {
    console.log(key);
}

// 値のみを取得
for (const value of fruitMap.values()) {
    console.log(value);
}

// エントリー([key, value]のペア)を取得
for (const [key, value] of fruitMap.entries()) {
    console.log(`${key}: ${value}`);
}

// または以下のように Mapオブジェクトのイテレータ にして処理を進める方法もある
const fruitMapIterator: MapIterator<[string, number]> = fruitMap[Symbol.iterator]();
for (const items of fruitMapIterator) {
  console.log(`${items[0]}: ${items[1]}`);
}
// 上記の出力部分を console.log(items); にすると以下結果
// > Array ["apple", 5]
// > Array ["banana", 3]
// > Array ["orange", 2]

Mapの利点

  • パフォーマンス:大量のデータの追加/削除が頻繁な場合に優れています
  • キーの型の柔軟性:任意の型をキーとして使用可能
  • イテレーションが簡単:順序が保証され、様々な反復メソッドが利用可能

利用例

  • キーと値のペアを管理する必要がある場合
// 例:ユーザーIDと名前の管理
const userMap: Map<number, string> = new Map();
userMap.set(1, "John");
userMap.set(2, "Alice");
  • 対象文字列における重複文字と重複回数のカウント
const charMap: Map<string, number> = new Map();
for (const char of "hello") {
    // 第二引数部分の処理: char がまだ存在しない(false の)場合は 0を指定し、
    // 既に存在する場合は取得した char の値(既存の値)に +1する
    charMap.set(char, (charMap.get(char) || 0) + 1);
}
console.log(charMap);
// Map { 'h' => 1, 'e' => 1, 'l' => 2, 'o' => 1 }

さいごに

Mapについて知っているようで全然知らなかったと実感しました。無知の知。
やはり、インプットした後はしっかりアウトプットして(かつ継続的に使用して)自身の血肉にしていくしかないですね。

ここまで読んでいただき、ありがとうございました。
何かお気づきの点などありましたらご教授ください。

Discussion

junerjuner

Map.prototype.entries()Map.prototype[Symbol.iterator]() と同義なので 直接 fo ... of に投げ込んでもよかったのでは無いでしょうか?
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/Symbol.iterator

benjuwanbenjuwan

@juner さん

コメント及び参照情報までありがとうございます!
Map.prototype[Symbol.iterator]()について知りませんでしたので大変勉強になりました。

以下のように試してみたところ同じような挙動になりました。

const fruitMap: Map<string, number> = new Map([
    ['apple', 5],
    ['banana', 3],
    ['orange', 2]
]);

- for (const [key, value] of fruitMap.entries()) {
-    console.log(`${key}: ${value}`);
- }

+ const fruitMapIterator = fruitMap[Symbol.iterator]();
+ for (const items of fruitMapIterator) {
+   console.log(`${items[0]}: ${items[1]}`);
+ }

記事にも反映させていただきます!