Mapとは
Map
オブジェクトはキーと値のペアを持つオブジェクトです。
データ構造はオブジェクトリテラルと似ていますが、Map
は特にキーが変動する連想配列に使えます。
使い方
コンストラクタから作成できます。
const map = new Map()
キーと値のペアを登録するにはset
を使います。
map.set('text1', 'Hello World!')
map.set('text2', 'JavaScript')
キーから値を取得するにはget
を使います。
map.get('text1') // 'Hello World!'
map.get('text2') // 'JavaScript'
よく使うメソッド
Map
オブジェクトからデータを取得したり操作するには、メソッドを使うのが一般的です。
ここではその中でもよく使うものを紹介します。
get
, set
この2つはMap
の最も基本的なメソッドで、値を登録したり取得したりできます。
-
get
: キー対応する値を取得する -
set
: キーと値のペアを登録する
const map = new Map()
map.set('key', 'value')
map.get('key') // 'value'
なお、get
で指定されたキーが見つからなかった場合、undefined
が返されます。
const map = new Map()
map.get('not-found') // undefined
has
Map
の中に与えられたキーが登録されているかを調べます。
戻り値は登録されているかを示すboolean
です。
const map = new Map()
map.set('key', 'value')
map.has('key') // true
map.has('not-found') // false
has
を使うと、キーが存在するかどうかで条件分岐ができます。
例えば、
- キーが存在する: その値を使って何か処理をする
- キーが存在しない: そのキーに何か値を登録する
といったことができます。
if (map.has(key)) { // 条件分岐
const value = map.get(key)
// ...
} else {
// ...
const value = // 登録したい値
map.set(value)
}
forEach
値とキーのペアに対して、与えられた関数(callbackFn
)を順番に実行します。
callbackFn
は以下の引数で実行されます。
-
value
: ペアの値 -
key
: ペアのキー -
map
:Map
オブジェクトそのもの
value
がkey
より先に来るので注意しでください。
const map = new Map()
map.set('key1', 10)
map.set('key2', 20)
map.forEach((value, key) => {
console.log(`${key}: ${value}`)
})
// key1: 10
// key2: 20
削除系
ペアの削除には2つのメソッドが使えます。
コンストラクタ
コンストラクタに反復可能オブジェクトを入れることで、Map
の初期値を設定できます。
要素は[キー, 値]
形式のArray-likeオブジェクトである必要があります。
反復可能やArray-likeには、例えばArray
があります。
配列を使うと、例えば以下のようにオブジェクトを作成できます。
const map = new Map([
['key', 'value'],
['key2', 10],
])
map.get('key') // 'value'
map.get('key2') // 10
Array-Like
ここでのArray-Likeとは、[]
を使って各要素にアクセスできるオブジェクトです。
また、コンストラクタで使われるのは以下のプロパティのようです。
-
0
: キー -
1
: 値
そのためコンストラクタの引数iterable
は、この2つのプロパティがあるオブジェクトを要素に取れます。
例えば以下のようなオブジェクトでも正常にMap
を作成できます。
new Map([{ 0: 'key', 1: 'value' }])
// Map(1) { 'key' => 'value' }
仕様書だとMap
から辿れるAddEntriesFromIterable
が該当しそうです。
The parameter iterable is expected to be an object that implements a %Symbol.iterator% method that returns an iterator object that produces a two element array-like object whose first element is a value that will be used as a Map key and whose second element is the value to associate with that key.
機械翻訳:
パラメータ iterable は、最初の要素がマップ・キーとして使用される値で、2 番目の要素がそのキーに関連付けられる値である 2 つの要素の配列のようなオブジェクトを生成するイテレータ・オブジェクトを返す %Symbol.iterator% メソッドを実装するオブジェクトであることが期待されます。
また、Array-Likeについてはこちらの記事も参考になると思います。
オブジェクトとの違い
大半の場合では、通常のオブジェクトリテラルでもMap
と似たようなことができます。
しかし、オブジェクトリテラルではなくMap
を使うべき場面もあります。
サイズを取得したい場合
Map
の場合、サイズはsize
プロパティから取得できます。
const map = new Map([
[1: 'ABC'],
[2: 'DEF'],
[3: 'GHI'],
])
map.size // 3
しかし、特定のオブジェクトにいくつの要素があるのかを、直接調べる方法はありません。
Object.keys()
から返される配列のlength
を調べる方法があります。
Object
内のアイテムの数を決定することは、より回りくどく、効率的ではありません。一般的な方法は、Object.keys()
から返される配列のlength
を通じて行う方法です。 - MDN
const obj = {
1: 'ABC',
2: 'DEF',
3: 'GHI'
}
Object.keys(obj).length // 3
ユーザーの入力を入れる場合
Map
のキーや値にユーザーの入力があるのは安全です。
const input = ['__proto__', 'console.log("Hello World!")']
map.set(input[0], input[1]) // OK
しかし、オブジェクトにユーザーの入力を入れた場合、予期せぬ挙動を引き起こす場合があります。
特にバリデーションなどを設けなかった場合、ユーザーは__proto__
やconstructor
など、JavaScriptの内部の動作に影響を及ぼす可能性のあるプロパティを書き換えられます。
これはオブジェクトインジェクション攻撃を引き起こす可能性があります。
const input = ['__proto__', 'console.log("Hello World!")']
map.set(input[0], input[1]) // OK
ユーザーが提供したキーと値のペアを Object に設定すると、攻撃者がオブジェクトのプロトタイプを上書きできる可能性があり、 オブジェクトインジェクション攻撃 につながる可能性があります。
偶発的なキーの問題と同様に、これもnull-prototypeオブジェクトを使用することによって軽減することができます。
シリアライズしたい場合
文字列にシリアライズしたい場合、オブジェクトのほうが簡単です。
オブジェクトはJSON.stringify
とJSON.parse
で簡単に文字列と変換できます。
const obj = { key: 'value' }
// JSON形式の文字列にできる
const str = JSON.stringify(obj) // '{"key":"value"}'
// 簡単にオブジェクトにできる
const obj2 = JSON.parse(str) // {key: 'value'}
しかし、Map
だとそう簡単にはいきません。
シリアライズしたい場合、そのような処理を自作するか、ライブラリに頼る必要があります。
シリアライズや解釈のためのネイティブな対応はありません。
キーの順番が重要な場合
詳細はこちらの記事が参考になると思います。
Map
の場合
キーはset
で挿入された順番で保管されます。
そのためforEach
メソッドなどでの反復処理の順番は、set
で登録された順番になります。
また、コンストラクタで追加された要素の順番も保証されます。
オブジェクトの場合
オブジェクトのプロパティの順序は複雑だそうです。
そのため、順序に依存する処理はあまり書かないほうが良いでしょう。
通常の Object のキーは現在では順序付けされていますが、以前はそうではなかったので、順序は複雑です。結果として、プロパティの順序に頼らない方が良いでしょう。
これ以外にもオブジェクトとMap
にはいくつか違いがあります。
詳細はMDNをご覧ください。