Map
===

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

試してみましょう
--------

解説
--

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

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

### キーの等価性

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

### Object と Map の比較

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

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

|  | Map | Object |
| --- | --- | --- |
| 偶発的なキー | `Map` は既定では何もキーを持っていません。明示的に設定したものだけを含みます。 | `Object` にはプロトタイプがあります。よって既定のキーを含んでいるので、注意しないと自分のキーと衝突する可能性があります。

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

 |
| セキュリティ | `Map` はユーザーが提供したキーと値を使用しても安全です。 | 
 ユーザーが提供したキーと値のペアを `Object` に設定すると、攻撃者がオブジェクトのプロトタイプを上書きできる可能性があり、
 オブジェクトインジェクション攻撃につながる可能性があります。偶発的なキーの問題と同様に、これも`null`-prototypeオブジェクトを使用することによって軽減することができます。
  |
| キーの型 | `Map` のキーはあらゆる値がなることができます
 （関数、オブジェクト、あらゆるプリミティブなど）。
  | `Object` のキーは文字列またはシンボルでなければなりません。 |
| キーの順序 | `Map` のキーは、単純で直感的な方法で順序付けられます。すなわち、反復処理を行うと、 `Map` オブジェクトは挿入順でキーを返します。 | 通常の `Object` のキーは現在では順序付けされていますが、以前はそうではなかったので、順序は複雑です。結果として、プロパティの順序に頼らない方が良いでしょう。

 この順序は ECMAScript 2015 で初めて自身のプロパティに対してのみ定義されましたが、 ECMAScript 2020 では継承されたプロパティに対しても同様に順序が定義されています。
 OrdinaryOwnPropertyKeys
 と
 EnumerateObjectProperties
 の抽象指定操作を参照してください。しかし、オブジェクトのプロパティが**すべて**反復処理される単一の単一のメカニズムはないことに注意してください。
 （`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` 風オブジェクトは、プロパティ `size` と、メソッド `entries()`、`forEach()`、`get()`、`has()`、`keys()`、`values()`、`@@iterator` を持ちます。
* 書き込み可能な `Map` 風オブジェクトは、これに加えてメソッド `clear()`、`delete()`、`set()` を持ちます。

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

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

* `AudioParamMap`
* `RTCStatsReport` (en-US)
* `EventCounts` (en-US)
* `KeyboardLayoutMap`
* `MIDIInputMap`
* `MIDIOutputMap`
コンストラクター
--------

`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 with JavaScript enabled. Enable JavaScript to view data.

関連情報
----

* `Map` のポリフィルは `core-js` で利用できます
* `Set`
* `WeakMap`
* `WeakSet`