🐥
【JavaScript / TypeScript】Mapについてまとめてみる
はじめに
MapはJavaScriptの組み込み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
Map.prototype.entries()
はMap.prototype[Symbol.iterator]()
と同義なので 直接 fo ... of に投げ込んでもよかったのでは無いでしょうか?@juner さん
コメント及び参照情報までありがとうございます!
Map.prototype[Symbol.iterator]()
について知りませんでしたので大変勉強になりました。以下のように試してみたところ同じような挙動になりました。
記事にも反映させていただきます!