Map

Map オブジェクトはキーと値のペアを保持し、キーが最初に挿入された順序を覚えています。キーや値には任意の値(オブジェクトとプリミティブ値)を使用することができます。

試してみましょう

解説

Map オブジェクトは、キーと値のペアのコレクションです。Map のキーは一度しか出現しませんMap の集合の中で一意です。Map オブジェクトはキーと値のペアで反復処理されます。for...of ループは、各反復処理に対して [キー, 値] という 2 つのメンバーからなる配列を返します。反復処理は 挿入順 で行われます。これは、それぞれのキーと値のペアが set() メソッドによって最初にマップに挿入された順番に対応します(つまり、 set() が呼ばれたときには、すでに同じ値を持つキーがマップになかったということです)。

仕様書では、「平均して、集合の要素数に対してサブリニアなアクセス時刻を提供する」マップを実装することを要求しています。したがって、複雑度が O(N) よりも高い場合、内部的にはハッシュ表(O(1) ルックアップ)、探索木(O(log(N)) ルックアップ)、あるいは他のデータ構造として表すことが可能です。

キーの等価性

キー値の等価性は SameValueZero アルゴリズムに基づいて評価されます。(以前は SameValue が使われており、 0-0 が異なるものとして扱われていました。ブラウザーの互換性をチェックしてください)。これは NaNNaN と等価と見なすもので(NaN !== NaN ですが)、他の値はすべて === 演算子の意味に従って等価性が考慮されます。

Object と Map の比較

ObjectMap は似ています。どちらもキーを値に設定したり、それらの値を受け取ったり、キーを削除したり、キーに何かが格納されているかどうかを判定したりすることができます。この意味で(そして他の組み込みオブジェクトがなかったため)、従来 ObjectMap として使われてきました。

しかし、いくつかの場面で Map の方が勝るような重要な違いがあります。

Map Object
偶発的なキー Map は既定では何もキーを持っていません。明示的に設定したものだけを含みます。

Object にはプロトタイプがあります。よって既定のキーを含んでいるので、注意しないと自分のキーと衝突する可能性があります。

メモ: ES5 では、 Object.create(null) を使用すると回避できますが、これはめったに行われていません。

セキュリティ Map はユーザーが提供したキーと値を使用しても安全です。

ユーザーが提供したキーと値のペアを Object に設定すると、攻撃者がオブジェクトのプロトタイプを上書きできる可能性があり、 オブジェクトインジェクション攻撃 につながる可能性があります。偶発的なキーの問題と同様に、これもnull-prototypeオブジェクトを使用することによって軽減することができます。

キーの型 Map のキーはあらゆる値がなることができます (関数、オブジェクト、あらゆるプリミティブなど)。 Object のキーは文字列またはシンボルでなければなりません。
キーの順序

Map のキーは、単純で直感的な方法で順序付けられます。すなわち、反復処理を行うと、 Map オブジェクトは挿入順でキーを返します。

通常の Object のキーは現在では順序付けされていますが、以前はそうではなかったので、順序は複雑です。結果として、プロパティの順序に頼らない方が良いでしょう。

この順序は ECMAScript 2015 で初めて自身のプロパティに対してのみ定義されましたが、 ECMAScript 2020 では継承されたプロパティに対しても同様に順序が定義されています。 OrdinaryOwnPropertyKeysEnumerateObjectProperties の抽象指定操作を参照してください。しかし、オブジェクトのプロパティがすべて反復処理される単一の単一のメカニズムはないことに注意してください。 (for-in は列挙可能な文字列キーのプロパティのみを含む、 Object.keys は自分自身の列挙可能な文字列キーのプロパティのみを含む、 Object.getOwnPropertyNames は列挙不可能な場合でも自分自身の文字列キーのプロパティを含む、 Object.getOwnPropertySymbols は、 Symbol キーを持つプロパティに対しても同じことを行う、など。)

大きさ

Map の中のアイテム数は、 size プロパティで簡単に得ることができます。 Object 内のアイテムの数を決定することは、より回りくどく、効率的ではありません。一般的な方法は、 Object.keys() から返される配列の length を通じて行う方法です。
反復処理 Map反復可能 ですので、直接反復処理を行うことができます。 Object では反復処理を行うのに、いくつかの形でキーの一覧を取得して、そのうえで反復処理を行う必要があります。

Object反復処理プロトコルを実装していないので、オブジェクトを JavaScript の for...of 文を使用して直接反復処理を行うことは(既定では)できません。

メモ:

  • オブジェクトに反復処理プロトコルを実装したり、 Object.keys または Object.entries を使用して反復処理を行うことはできます。
  • for...in 文により、オブジェクトの列挙可能なプロパティを反復処理することができます。
性能

キーと値のペアを頻繁に追加したり削除したりすることが求められる場面では、性能がより良くなります。

キーと値のペアを頻繁に追加したり削除したりすることに最適化されていません。

シリアライズと解釈

シリアライズや解釈のためのネイティブな対応はありません。

(ただし、 replacer 引数で JSON.stringify() を使用し、 reviver 引数で JSON.parse() を使用することで、 Map のために、独自のシリアライズと解釈の対応を作成することができます。 Stack Overflow の質問 How do you JSON.stringify an ES6 Map? を参照してください。)

Object から JSON へのシリアライズには、 JSON.stringify() を使用してネイティブに対応しています。

JSON から Object への解釈には、 JSON.parse() を使用してネイティブに対応しています。

オブジェクトプロパティの設定

Map オブジェクトに対してオブジェクトプロパティを設定すると正しく動作しますが、混乱を催すことが考えられます。

たとえば、次の例は一応動作するように見えます。

js
const wrongMap = new Map();
wrongMap["bla"] = "blaa";
wrongMap["bla2"] = "blaaa2";

console.log(wrongMap); // Map { bla: 'blaa', bla2: 'blaaa2' }

しかし、このようにプロパティを設定すると、 Map データ構造を使用しません。一般的なオブジェクトの機能を使用します。 'bla' の値はクエリーを行うための Map に格納されません。データにその他の操作を行うと失敗します。

js
wrongMap.has("bla"); // false
wrongMap.delete("bla"); // false
console.log(wrongMap); // Map { bla: 'blaa', bla2: 'blaaa2' }

Map にデータを格納する正しい方法は、 set(key, value) メソッドを使用する方法です。

js
const contacts = new Map();
contacts.set("Jessie", { phone: "213-555-1234", address: "123 N 1st Ave" });
contacts.has("Jessie"); // true
contacts.get("Hilary"); // undefined
contacts.set("Hilary", { phone: "617-555-4321", address: "321 S 2nd St" });
contacts.get("Jessie"); // {phone: "213-555-1234", address: "123 N 1st Ave"}
contacts.delete("Raymond"); // false
contacts.delete("Jessie"); // true
console.log(contacts.size); // 1

Map 風のブラウザー API

ブラウザーの Map 風オブジェクト (または「Map 風オブジェクト」) は、多くの点で Map のようにふるまう Web API のインターフェイスです。

Map と同様に、エントリーはオブジェクトに追加されたのと同じ順番で走査できます。 また、Map 風オブジェクトと Map は同じ名前で同じ挙動のプロパティおよびメソッドを持ちます。 しかし、Map と違い、各エントリーのキーと値には事前に定義された特定の型しか使用できません。

使用可能な型は、仕様書の IDL 定義で設定されています。 たとえば、RTCStatsReport (en-US) はキーには文字列を、値にはオブジェクトを用いなければならない Map 風オブジェクトです。 これは、仕様書の IDL で以下のように定義されています。

webidl
interface RTCStatsReport {
  readonly maplike<DOMString, object>;
};

Map 風オブジェクトは読み取り専用のことも、読み書き可能なこともあります。(上の IDL の readonly キーワードに注目してください)

これらのメソッドとプロパティは、キーと値の型の制約を除いて、Map の対応するエンティティと同様にふるまいます。

以下は、読み取り専用の Map 風ブラウザーオブジェクトの例です。

コンストラクター

Map()

新しい Map オブジェクトを生成します。

静的プロパティ

get Map[@@species]

派生クラスを生成するためのコンストラクター関数です。

インスタンスプロパティ

Map.prototype[@@toStringTag]

@@toStringTag プロパティの初期値は文字列 "Map" です。このプロパティは Object.prototype.toString() で使用されます。

Map.prototype.size

Map オブジェクトの中のキーと値のペアの数を返します。

インスタンスメソッド

Map.prototype.clear()

Map オブジェクトからすべてのキーと値のペアを削除します。

Map.prototype.delete()

Map オブジェクトに要素が存在し、削除された場合は true を返します。要素が存在しなければ false を返します。その後では Map.prototype.has(key)false を返すようになります。

Map.prototype.get()

key で指定されたキーに結び付けられた値を返します。存在しない場合は undefined を返します。

Map.prototype.has()

論理値で、渡されたキーに結び付けられた要素が Map オブジェクト内に存在するかどうかを返します。

Map.prototype.set()

Map オブジェクト内の key で指定されたキーの値を value に設定します。その Map オブジェクトを返します。

Map.prototype[@@iterator]()

Map オブジェクト内の各要素の [key, value] の配列 が挿入順で含む、新しいイテレーターオブジェクトを返します。

Map.prototype.keys()

Map オブジェクト内の各要素のキーが挿入順で含む、新しいイテレーターオブジェクトを返します。

Map.prototype.values()

Map オブジェクト内の各要素の値が挿入順で含む、新しいイテレーターオブジェクトを返します。

Map.prototype.entries()

Map オブジェクト内の要素に対して挿入順にすべての要素の [key, value] の配列を含む、新しいイテレーターオブジェクトを返します。

Map.prototype.forEach()

callbackFn を、 Map オブジェクトに存在するそれぞれのキーと値のペアに対して 1 回ずつ、挿入順に呼び出します。 thisArg 引数が forEach に与えられた場合は、それぞれのコールバックでこれを this の値として使用します。

Map オブジェクトの使用

js
const myMap = new Map();

const keyString = '文字列'
const keyObj = {};
const keyFunc  function() {};

// 値を設定する
myMap.set(keyString, "'文字列' と結び付けられた値");
myMap.set(keyObj, 'keyObj と結び付けられた値');
myMap.set(keyFunc, 'keyFunc と結び付けられた値');

console.log(myMap.size); // 3

// 値を取得する
console.log(myMap.get(keyString)); // "'文字列' と結び付けられた値"
console.log(myMap.get(keyObj)); // "keyObj と結び付けられた値"
console.log(myMap.get(keyFunc)); // "keyFunc と結び付けられた値"

console.log(myMap.get('文字列')); // "'文字列' と結び付けられた値"。 keyString === '文字列' であるため
console.log(myMap.get({})); // undefined, keyObj !== {} であるため
console.log(myMap.get(function() {})); // undefined, keyFunc !== function () {} であるため

NaN を Map のキーとして使用

NaN もまたキーとして使うことができます。すべての NaN は自身と等しくない(NaN !== NaN は真)にもかかわらず、以下の例は動作します。これは NaN が互いに区別できないためです。

js
const myMap = new Map();
myMap.set(NaN, "not a number");

myMap.get(NaN);
// "not a number"

const otherNaN = Number("foo");
myMap.get(otherNaN);
// "not a number"

for..of を使用した Map の反復処理

マップは for..of ループを使用して反復処理を行うことができます。

js
const myMap = new Map();
myMap.set(0, "zero");
myMap.set(1, "one");

for (const [key, value] of myMap) {
  console.log(`${key} = ${value}`);
}
// 0 = zero
// 1 = one

for (const key of myMap.keys()) {
  console.log(key);
}
// 0
// 1

for (const value of myMap.values()) {
  console.log(value);
}
// zero
// one

for (const [key, value] of myMap.entries()) {
  console.log(`${key} = ${value}`);
}
// 0 = zero
// 1 = one

forEach() で Map を反復処理

マップは forEach() メソッドを使用して反復できます。

js
myMap.forEach((value, key) => {
  console.log(`${key} = ${value}`);
});
// 0 = zero
// 1 = one

Array オブジェクトとの関係

js
const kvArray = [
  ["キー1", "値1"],
  ["キー2", "値2"],
];

// 通常の Map コンストラクターを使って、キーと値の 2 次元配列をマップに変換する
const myMap = new Map(kvArray);

console.log(myMap.get("キー1")); // "値1" を返す

// 展開演算子を使って、マップをキー・値の 2 次元配列に変換する
console.log(Array.from(myMap)); // kvArray とまったく同じ Array を表示する

// あるいは展開演算子をキーまたは値のイテレーターに使って、キーまたは値のみの配列を得る
console.log([...myMap]);

// または keys() や values() のイテレーターを使用して配列に変換する
console.log(Array.from(myMap.keys())); // ["key1", "key2"] が出力される

Map の複製と混合

Array と同様に、 Map は複製することができます。

js
const original = new Map([[1, "one"]]);

const clone = new Map(original);

console.log(clone.get(1)); // one
console.log(original === clone); // false (useful for shallow comparison)

メモ: データ自身は複製されないことに注意しておいてください。

マップはキーの固有性を保持しながら混合可能です。

js
const first = new Map([
  [1, "one"],
  [2, "two"],
  [3, "three"],
]);

const second = new Map([
  [1, "uno"],
  [2, "dos"],
]);

// 2 つのマップを混合します。重複するキーは後勝ちになります。
// スプレッド演算子は基本的に Map を Array に変換します。
const merged = new Map([...first, ...second]);

console.log(merged.get(1)); // uno
console.log(merged.get(2)); // dos
console.log(merged.get(3)); // three

Map は Array と混合することもできます。

js
const first = new Map([
  [1, "one"],
  [2, "two"],
  [3, "three"],
]);

const second = new Map([
  [1, "uno"],
  [2, "dos"],
]);

// マップと配列を混合します。重複するキーは後勝ちになります。
const merged = new Map([...first, ...second, [1, "eins"]]);

console.log(merged.get(1)); // eins
console.log(merged.get(2)); // dos
console.log(merged.get(3)); // three

仕様書

Specification
ECMAScript Language Specification
# sec-map-objects

ブラウザーの互換性

BCD tables only load in the browser

関連情報