【STEP.23】
マップ(Map)の使い方をマスターしよう!

Mapオブジェクトとは

Mapオブジェクトは、キー/値のセット、つまり連想配列(ハッシュ)を管理するためのオブジェクトです。

従来のJavaScriptでは、オブジェクトリテラルで連想配列を管理するのが基本でした。

しかし、ES2015でようやく、専用のオブジェクトが提供されることとなりました。

Mapオブジェクトのメンバー一覧

Mapオブジェクトで利用可能なメンバーは、以下の通りです。

【Mapオブジェクトで利用可能なメンバー】
メンバー概要
size要素数
set(key, val)キーkey/値valペアの要素を追加。重複時は上書き。
get(key)指定したキーの要素を取得
has(key)指定したキーの要素が存在するかを判定
delete(key)指定したキーの要素を削除
clear()すべての要素を削除
keys()すべてのキーを取得
values()すべての値を取得
entries()すべてのキー/値を取得
forEach(fnc [,that])マップ内の要素を関数fncで順に処理

Mapオブジェクトとオブジェクトリテラルの違い

Mapオブジェクトとオブジェクトリテラルの違いを確認しましょう。

任意の型でキーを設定できる

オブジェクトリテラルでは、あくまでプロパティ名をキーとして代替していたので、キーとして利用できるのは文字列だけでした。

しかし、Mapオブジェクトでは任意の型をキーとして利用できます。

たとえば、オブジェクトやNaNですら、キーになりえます。

マップのサイズを取得できる

Mapオブジェクトではsizeプロパティを使って、登録されたキー/値の個数を取得できます。

しかし、オブジェクトリテラルには、そうした仕組みはありません。

for…in命令などでオブジェクトを走査し、手動でカウントする必要があります。

クリーンなマップを作成できる

オブジェクトリテラルでは、その実体はObjectオブジェクトです。

配下には、Objectオブジェクトが標準で用意しているプロパティ(キー)が最初から存在します。

空のオブジェクトリテラルを作成した時点で、すでに空ではないということです。

しかし、Mapオブジェクトはそれ専用のオブジェクトなので、完全に空の連想配列を生成できます。

Objectオブジェクトでもcreateメソッドを利用すれば、強制的に空のオブジェクトを生成することもできますが、クリーンなマップを生成するならば、素直にMapオブジェクトを利用すべきでしょう。

パフォーマンスに優れる

キー/値を頻繁に削除するような場合には、それ専用の仕組みであるMapオブジェクトのほうがパフォーマンスに優れています。

ただし、以上のようなメリットにもかかわらず、歴史的な経緯、また、リテラルでコンパクトに表現できるという理由から、連想配列としてオブジェクトリテラルを利用する状況も少なくありません。

メリット/デメリットを理解しながら双方を使い分ける必要があります。

Mapオブジェクトを利用する上での注意点

Mapオブジェクトでは任意の型でキーを設定できます。

結果、キーを指定する際には、以下の点に注意しなければなりません。

キーは「===」演算子で比較される

Mapオブジェクトでキーを比較する際には===演算子が利用されます。

よって、以下のようなコードは意図した結果が得られません。

let m = new Map();
m.set('1','hoge');
console.log(m.get(1));
// 結果:undefined

この例では、「m.set('1','hoge');」のキー「1」が文字列であるのに対して、「console.log(m.get(1));」のキー「1」が数値であることから、意図した値にアクセスできていません。

「キーが一致しているように見えるのに意図した値が得られない」という場合には、データ型の不一致を疑ってみると良いでしょう。

特別なNaNは特別でない

NaNは自分自身とも等しくない特別な値です。(つまり、NaN !== NaNです。)

しかし、Mapの世界では、例外的にNaN === NaNと見なされます。

よって、以下のコードも、正しくキーNaNに対応する値を得られます。

let m = new Map();
m.set(NaN,'hoge');
console.log(m.get(NaN));
// 結果:hoge

オブジェクトの比較には要注意

たとえば、オブジェクトをキーとした以下のコードは、どのような結果を得られるでしょうか?

let m = new Map();
m.set({},'hoge');
console.log(m.get({}));
// 結果:???

「同じく空のオブジェクトリテラル{}を指しているので、結果は「hoge」となる」と考えた人は不正解です。

「オブジェクトのような参照型を比較した場合、参照での比較になる」からです。

見た目が同じであっても、異なる場所で生成されたオブジェクトであれば、それは異なるものと見なされるのです。

よって、上のコードはundefinedを返します。

もしも正しくキー「{}」を認識させたいならば、以下のようにします。

今度は、正しく値を得られることが確認できます。

let key = {};
let m = new Map();
m.set(key,'hoge');
console.log(m.get(key));
// 結果:hoge

マップに値を設定する【Mapコンストラクター/set】

マップに値を設定するにはMapコンストラクターを使用するかsetメソッドを利用します。

Mapコンストラクターを利用する方法

Mapコンストラクターの構文は次の通りです。

Mapコンストラクター

new Map([[key, value],...])  
key     キー
value   値

キー/値のセットを二次元配列の形式で渡していきます。

以下の例でもキーと値とが組みになったマップが生成されていることが確認できます。

let m = new Map([
  ['Fl','フルート'],
  ['Tp','トランペット'],
  ['Vn','ヴァイオリン'],
]);
console.log(m);

setメソッドを利用する方法

Mapオブジェクトには、コンストラクターで初期化する他にsetメソッドで、個々にキー/値を設定することもできます。

また、マップ内の値にアクセスするには、getメソッドを利用します。

set/getメソッド

map.set(key, value)
map.get(key)
key      キー
value    値

具体的なコード例は以下の通りです。

let m = new Map();
m.set('Fl','フルート');
console.log(m.get('Fl'));
// 結果:フルート

マップにキーが存在するかどうかを判定する【has】

hasメソッドを利用します。

hasメソッド

map.has(key)
key    キー

マップにキーが存在する場合、hasメソッドはtrueを、さもなくばfalseを返します。

let m = new Map();
m.set('FL','フルート');
console.log(m.has('FL'));
// 結果:true
console.log(m.has('CL'));
// 結果:false

なお、getメソッドは指定されたキーが存在しない場合にundefinedを返します。

そのため、getメソッドでもキーの有無を判定できますが、一般的にはより直接的な手段として、hasメソッドを利用します。

マップから既存のキーを削除する【delete】

特定のキーを削除するには、deleteメソッドを利用します。

deleteメソッド

map.delete(key)
key    キー

deleteメソッドは、キーを削除できたらtrueを返します。(キーが存在しない場合などで削除できなかった場合はfalseを返します。)

let m = new Map();
	
m.set('Fl','フルート');
m.set('Tp','トランペット');
	
console.log(m.delete('Fl'));
// 結果:true
console.log(m.delete('Cl'));
// 結果:false

特定のキーではなく全てのキーを削除したい場合には、clearメソッドを利用します。

m.clear();

マップからすべてのキー/値を取り出す【keys/values/entries】

次に掲げる表のようなメソッドが用意されています。

【マップのキー/値を取得するためのメソッド】
メソッド概要
keysすべてのキーを取得
valuesすべての値を取得
entriesすべてのキー/値を取得

以下は、それぞれのメソッドを使って、マップ内のすべてのキー、値を取得する例です。

let m = new Map();
	
m.set('Fl','フルート');
m.set('Tp','トランペット');
m.set('Vn','ヴァイオリン');
	
// キーを順に取得
for (let key of m.keys()) {
  console.log(key);
  // 結果:Fl, Tp, Vn
}
	
// 値を順に取得
for (let value of m.values()) {
  console.log(value);
  // 結果:フルート, トランペット, ヴァイオリン
}
	
for (let [key, value] of m.entries()) {
  console.log(key + ':' + value);
  // 結果:Fl:フルート, Tp:トランペット, Vn:ヴァイオリン
}

entriesメソッドは「[キー,値]」のペアを配列として返します。

よって、for…of命令の仮引数でも「let [key, value]」のようにキー/値のセットを受け取れるようにします。

for (let [key, value] of m.entries()) 」の部分は「for (let [key, value] of m)」としても同じ意味です。(内部的にはentriesメソッドが呼び出されます。)

マップの内容を順に処理する【forEach】

別解として、forEachメソッドでマップの内容を順に取り出すこともできます。

forEachメソッド

map.forEach(function(value, key, map) {
  ...statements...
},that)
value       値
key         キー
map         元のマップ
statements  要素に対する処理
that        thisが表す値

たとえば以下は、上記のソースコードの「for (let [key, value] of m.entries()) {...}/ソースコード19~22行目」の部分をforEachメソッドで書き替えたものです。

コールバック関数の引数は不用であれば、省略してもかまいません。(ここではkeyvalueのみを渡しています。)

let m = new Map();
	
m.set('Fl','フルート');
m.set('Tp','トランペット');
m.set('Vn','ヴァイオリン');

m.forEach(function (value, key){
  console.log(key + ':' + value);
  // 結果:Fl:フルート, Tp:トランペット, Vn:ヴァイオリン		
});

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です