今や、私たちは次のような複雑なデータ構造を知っています:
- キー付けされたコレクションを格納するオブジェクト
- 順序付けされたコレクションを格納する配列
しかし、実際にはこれだけでは不十分です。そのために、Map や Set が存在します。
Map
Map は Object と同じように、キー付されたデータ項目の集まりです。主な違いは Map は任意の型のキーを許可することです。
主なメソッドは次の通りです:
new Map()– 新しい map を作ります.map.set(key, value)– キーで、値を格納します.map.get(key)– 指定されたキーの値を返却します。map に存在しないkeyの場合にはundefinedになります.map.has(key)– キーが存在する場合にtrueを返します。そうでなければfalseになります.map.delete(key)– キーで値を削除します.map.clear()– map をクリアします.map.size– 現在の要素の数です.
例:
let map = new Map();
map.set('1', 'str1'); // 文字列キー
map.set(1, 'num1'); // 数値キー
map.set(true, 'bool1'); // 真偽値キー
// 通常のオブジェクトを覚えていますか?キーを文字列に変換していました。
// Map は型を維持します。なので、これらは別ものです:
alert( map.get(1) ); // 'num1'
alert( map.get('1') ); // 'str1'
alert( map.size ); // 3
上の通り、オブジェクトとは違い、キーは文字列には変換されません。任意の型のキーが利用可能です。
map[key] は Map を使用する正しい方法ではありません例えば、map[key] = 2 のように、map[key] でも動作しますが、これは map を通常の JavaScript オブジェクトとして扱っているので、対応するすべての制限(文字列/シンボルキーのみなど)があることを意味します。
なので、map メソッドを使用するべきです: set, get など.
Map はキーとしてオブジェクトを使うこともできます。
例:
let john = { name: "John" };
// 各ユーザに対し、訪問回数を保持しましょう
let visitsCountMap = new Map();
// john は map のキーです
visitsCountMap.set(john, 123);
alert( visitsCountMap.get(john) ); // 123
オブジェクトをキーとして使用することは、最も注目に値する重要な Map の機能の1つです。同じことを Object で行うことはできません。Object ではキーとして文字列を使用することは問題ありませんが、オブジェクトを使用することはできません。
やってみましょう:
let john = { name: "John" };
let ben = { name: "Ben" };
let visitsCountObj = {}; // オブジェクトを使用
visitsCountObj[ben] = 234; // ben オブジェクトをキーに使用
visitsCountObj[john] = 123; // john オブジェクトをキーに使用。ben オブジェクトは置換されます
// That's what got written!
alert( visitsCountObj["[object Object]"] ); // 123
visitsCountObj はオブジェクトなので、john などのすべてのキーを文字列に変換します。そのため、文字列キー "[object Object]" となります。間違いなくこれは望むものではありません。
Map はどのようにキーを比較するか等価のテストをするために、Map は SameValueZero アルゴリズムを使います。大雑把には厳密等価 === と同じですが、違いは NaN は NaN と等しいとみなされる点です。なので、NaN も同様にキーとして使うことができます。
このアルゴリズムは変更したりカスタマイズすることはできません。
すべての map.set 呼び出しは map 自身を返すので、呼び出しを “チェーン” することができます:
map.set('1', 'str1')
.set(1, 'num1')
.set(true, 'bool1');
Map での繰り返し/ループ
map でループするためには3つのメソッドがあります:
map.keys()– キーに対する iterable を返します。map.values()– 値に対する iterable を返します。map.entries()– エントリ[key, value]の iterable を返します。これはfor..ofでデフォルトで使われます。
例:
let recipeMap = new Map([
['cucumber', 500],
['tomatoes', 350],
['onion', 50]
]);
// キー(野菜)の反復
for (let vegetable of recipeMap.keys()) {
alert(vegetable); // cucumber, tomateos, onion
}
// 値(量)の反復
for (let amount of recipeMap.values()) {
alert(amount); // 500, 350, 50
}
// [key, value] エントリーの反復
for (let entry of recipeMap) { // recipeMap.entries() と同じ
alert(entry); // cucumber,500 (など)
}
繰り返しは値が挿入された順で行われます。通常の Object とは違い、Map はこの順番を保持しています。
それに加えて、Map は Array と同じように、組み込みの forEach メソッドを持っています。
// 各 (key, value) ペアに対して関数を実行
recipeMap.forEach( (value, key, map) => {
alert(`${key}: ${value}`); // cucumber: 500 etc
});
Object.entries: オブジェクトから Map を生成
Map を生成する時、キー/値のペアをもつ配列(または別の反復可能(iterable)) を渡すことができます:
// [key, value] ペアの配列
let map = new Map([
['1', 'str1'],
[1, 'num1'],
[true, 'bool1']
]);
alert( map.get('1') ); // str1
通常のオブジェクトがあり、そこから Map を生成したい場合は、オブジェクトのキー/値のペアの配列を、その形式で返す組み込みのメソッド Object.entries(obj) を使用できます。
なので、次のようにオブジェクトから map の初期化をすることができます:
let obj = {
name: "John",
age: 30
};
let map = new Map(Object.entries(obj));
alert( map.get('name') ); // John
ここで、Object.entries はキー/値のペアの配列を返します: [ ["name","John"], ["age", 30] ]。これは Map が必要とするものです。
Object.fromEntries: Map から オブジェクト
つい先程、通常のオブジェクトから Object.entries(obj) を使用して Map を作成する方法を見ました。
逆のことをする Object.fromEntries メソッドもあります。: [key, value] ペアの配列が与えられ、そこからオブジェクトを作成します:
let prices = Object.fromEntries([
['banana', 1],
['orange', 2],
['meat', 4]
]);
// prices = { banana: 1, orange: 2, meat: 4 }
alert(prices.orange); // 2
Map から通常のオブジェクトを取得する際に Object.fromEntries が使えます。
E.g. Map にデータを保持しているが、通常のオブジェクトを期待するサードパーティのコードにわたす必要がある場合。
やってみましょう:
let map = new Map();
map.set('banana', 1);
map.set('orange', 2);
map.set('meat', 4);
let obj = Object.fromEntries(map.entries()); // 通常のオブジェクトを作成します (*)
// 完了!
// obj = { banana: 1, orange: 2, meat: 4 }
alert(obj.orange); // 2
map.entries() への呼び出しはキー/値ペアの配列を返し、それはまさに Object.fromEntries の正しい形式です。
また、行 (*) をより短くすることもできます:
let obj = Object.fromEntries(map); // omit .entries()
これは同じことです。なぜなら、Object.fromEntries は引数に反復可能なオブジェクトを期待するからです。つまり配列である必要はありません。そして、map の標準のイテレーションは map.entries() と同じキー/値を返します。したがって、map と同じキー/値を持つプレーンなオブジェクトが取得できます。
Set
Set は、特別タイプのコレクションで、“値の集合” (キーなし)です。それぞれの値は一度しか現れません。
主なメソッドは次の通りです:
new Set(iterable)– set を作ります。オプションで値の配列(任意の iterable が指定可能)からも可能です。set.add(value)– 値を追加し、 set 自身を返します。set.delete(value)– 値を削除し、valueが呼び出し時に存在すればtrue, そうでなければfalseを返します。set.has(value)– set の中に値が存在すればtrueを返し、それ以外はfalseです。set.clear()– set から全てを削除します。set.size– set の要素数です。
主な特徴は同じ値での set.add(value) の繰り返しの呼び出しでは何もしないことです。Set では各値は一度しか現れないためです。
例えば、訪問者全員を覚えておきたいです。が、繰り返し訪問しても重複しないようにしたいです。訪問者は一度だけ “カウント” される必要があります。
Set はそれに相応しいものです:
let set = new Set();
let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };
// 訪問、何度も来るユーザもいます
set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);
// set はユニークな値のみをキープします
alert( set.size ); // 3
for (let user of set) {
alert(user.name); // John (そして Pete と Mary)
}
Set の代替は、ユーザの配列と arr.find を使って挿入毎に重複をチェックするコードです。しかし、このメソッドはすべての要素をチェックするため配列全体を見ます。そのためパフォーマンスははるかに悪いです。Set は一意性チェックを高速に行うよう、内部的に最適化されています。
Set での繰り返し
for..of または forEach を使うことで set をループすることができます:
let set = new Set(["oranges", "apples", "bananas"]);
for (let value of set) alert(value);
// forEach と同じ:
set.forEach((value, valueAgain, set) => {
alert(value);
});
面白い点に注意してください。Set の中の forEach 関数は3つの引数を持っています: 値(value), 次に 再び値(valueAgain), 次にターゲットのオブジェクトです。実際、引数には同じ値が2回出現します。
これは forEach が3つの引数をもつ Map との互換性のために作られています。少しおかしく見えるかもしれません。が、特定ケースにおいて簡単に Map を Set に、またその逆を行うのに役立ちます。
Map がイテレーションのために持っているメソッドと同じメソッドもサポートしています:
set.keys()– 値に対する iterable なオブジェクトを返します。set.values()–set.keysと同じで、Mapとの互換性のためです。set.entries()–[value, value]のエントリのための iterable なオブジェクトを返します。Mapの互換性のために存在します。
サマリ
Map – はキー付けされた値のコレクションです。
メソッドとプロパティです:
new Map([iterable])– 初期化ではオプションで[key,value]ペアのiterable(e.g. 配列 ) で map を作成しますmap.set(key, value)– キーで値を保存しますmap.get(key)– キーで値を返します。keyが map にない場合はundefinedが返りますmap.has(key)–keyが存在すればtrueを、そうでなければfalseを返しますmap.delete(key)– 指定されたキーで値を削除しますmap.clear()– map からすべてを削除しますmap.size– 現在の要素数を返します
通常の Object との違いです:
- 任意のキー、オブジェクトをキーにすることができます。
- 追加の便利なメソッド、
sizeプロパティ。
Set – はユニーク値のコレクションです。
メソッドとプロパティです:
new Set([iterable])– set を作ります。オプションで値の配列(任意の iterable が指定可能)からも可能です。set.add(value)– 値を追加し、 set 自身を返します。set.delete(value)– 値を削除し、valueが呼び出し時に存在すればtrue, そうでなければfalseを返します。set.has(value)– set の中に値が存在すればtrueを返し、それ以外はfalseです。set.clear()– set から全てを削除します。set.size– set の要素数です。
Map と Set のイテレーションは常に挿入順で行われます。そのため、これらのコレクションが順序付けられていないとは言えませんが、要素を並べ替えたり、その番号で要素を直接取得することはできません。
コメント
<code>タグを使ってください。複数行の場合は<pre>を、10行を超える場合にはサンドボックスを使ってください(plnkr, JSBin, codepen…)。