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 は既定では何もキーを持っていません。明示的に設定したものだけを含みます。 |
メモ: ES5 では、
|
セキュリティ | Map はユーザーが提供したキーと値を使用しても安全です。 |
ユーザーが提供したキーと値のペアを |
キーの型 |
Map のキーはあらゆる値がなることができます
(関数、オブジェクト、あらゆるプリミティブなど)。
|
Object のキーは文字列またはシンボルでなければなりません。 |
キーの順序 |
|
通常の
この順序は ECMAScript 2015 で初めて自身のプロパティに対してのみ定義されましたが、 ECMAScript 2020 では継承されたプロパティに対しても同様に順序が定義されています。
OrdinaryOwnPropertyKeys
と
EnumerateObjectProperties
の抽象指定操作を参照してください。しかし、オブジェクトのプロパティがすべて反復処理される単一の単一のメカニズムはないことに注意してください。
( |
大きさ |
Map の中のアイテム数は、 size プロパティで簡単に得ることができます。 |
Object 内のアイテムの数を決定することは、より回りくどく、効率的ではありません。一般的な方法は、 Object.keys() から返される配列の length を通じて行う方法です。 |
反復処理 | Map は 反復可能 ですので、直接反復処理を行うことができます。 |
Object
では反復処理を行うのに、いくつかの形でキーの一覧を取得して、そのうえで反復処理を行う必要があります。
メモ:
|
性能 |
キーと値のペアを頻繁に追加したり削除したりすることが求められる場面では、性能がより良くなります。 |
キーと値のペアを頻繁に追加したり削除したりすることに最適化されていません。 |
シリアライズと解釈 |
シリアライズや解釈のためのネイティブな対応はありません。 (ただし、 replacer 引数で |
JSON から |
オブジェクトプロパティの設定
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
風ブラウザーオブジェクトの例です。
コンストラクター
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('a string')); // "'文字列' と結び付けられた値"。 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