これまで、以下の複合データ構造について学習してきました。
- オブジェクトは、キー付きコレクションの格納に使用されます。
- 配列は、順序付けられたコレクションの格納に使用されます。
しかし、それは現実世界では不十分です。そのため、MapとSetも存在します。
Map
Mapは、Objectのように、キー付きデータ項目のコレクションです。しかし、主な違いは、Mapはあらゆるタイプのキーを許可することです。
メソッドとプロパティは以下のとおりです。
new Map()– マップを作成します。map.set(key, value)– キーで値を格納します。map.get(key)– キーで値を返します。キーがマップに存在しない場合はundefinedを返します。map.has(key)– キーが存在する場合はtrue、存在しない場合はfalseを返します。map.delete(key)– キーで要素(キーと値のペア)を削除します。map.clear()– マップからすべてを削除します。map.size– 現在の要素数を返します。
例えば
let map = new Map();
map.set('1', 'str1'); // a string key
map.set(1, 'num1'); // a numeric key
map.set(true, 'bool1'); // a boolean key
// remember the regular Object? it would convert keys to string
// Map keeps the type, so these two are different:
alert( map.get(1) ); // 'num1'
alert( map.get('1') ); // 'str1'
alert( map.size ); // 3
ご覧のように、オブジェクトとは異なり、キーは文字列に変換されません。あらゆるタイプのキーが可能です。
map[key]は、Mapを使用する正しい方法ではありません。map[key]も動作しますが(例えば、map[key] = 2と設定できます)、これはmapを通常のJavaScriptオブジェクトとして扱っているため、対応するすべての制限(文字列/シンボルキーのみなど)が適用されます。
そのため、set、getなどのmapメソッドを使用する必要があります。
Mapは、オブジェクトをキーとして使用することもできます。
例えば
let john = { name: "John" };
// for every user, let's store their visits count
let visitsCountMap = new Map();
// john is the key for the map
visitsCountMap.set(john, 123);
alert( visitsCountMap.get(john) ); // 123
オブジェクトをキーとして使用することは、最も注目すべきで重要なMapの機能の1つです。Objectでは同じことはできません。Objectにおける文字列キーは問題ありませんが、別のObjectをObjectのキーとして使用することはできません。
試してみましょう
let john = { name: "John" };
let ben = { name: "Ben" };
let visitsCountObj = {}; // try to use an object
visitsCountObj[ben] = 234; // try to use ben object as the key
visitsCountObj[john] = 123; // try to use john object as the key, ben object will get replaced
// That's what got written!
alert( visitsCountObj["[object Object]"] ); // 123
visitsCountObjはオブジェクトであるため、上記のjohnやbenなどのすべてのObjectキーを同じ文字列"[object Object]"に変換します。明らかに、これは望ましい動作ではありません。
キーの等価性をテストするために、MapはSameValueZeroアルゴリズムを使用します。これは厳密な等価性===とほぼ同じですが、NaNはNaNと等しいと見なされる点が異なります。そのため、NaNもキーとして使用できます。
このアルゴリズムを変更またはカスタマイズすることはできません。
すべてのmap.set呼び出しはマップ自体を返すため、呼び出しを「チェーン」できます。
map.set('1', 'str1')
.set(1, 'num1')
.set(true, 'bool1');
Mapの反復処理
mapをループ処理するには、3つのメソッドがあります。
map.keys()– キーのイテラブルを返します。map.values()– 値のイテラブルを返します。map.entries()– エントリ[key, value]のイテラブルを返します。for..ofではデフォルトで使用されます。
例えば
let recipeMap = new Map([
['cucumber', 500],
['tomatoes', 350],
['onion', 50]
]);
// iterate over keys (vegetables)
for (let vegetable of recipeMap.keys()) {
alert(vegetable); // cucumber, tomatoes, onion
}
// iterate over values (amounts)
for (let amount of recipeMap.values()) {
alert(amount); // 500, 350, 50
}
// iterate over [key, value] entries
for (let entry of recipeMap) { // the same as of recipeMap.entries()
alert(entry); // cucumber,500 (and so on)
}
反復処理は、値が挿入された順序と同じ順序で行われます。Mapは、通常のObjectとは異なり、この順序を保持します。
それに加えて、Mapには、Arrayと同様の組み込みforEachメソッドがあります。
// runs the function for each (key, value) pair
recipeMap.forEach( (value, key, map) => {
alert(`${key}: ${value}`); // cucumber: 500 etc
});
Object.entries: オブジェクトからMapを作成
Mapを作成する場合、初期化のためにキーと値のペアを含む配列(またはその他のイテラブル)を渡すことができます。
// array of [key, value] pairs
let map = new Map([
['1', 'str1'],
[1, 'num1'],
[true, 'bool1']
]);
alert( map.get('1') ); // str1
通常のオブジェクトがあり、そこからMapを作成したい場合は、オブジェクトのキーと値のペアの配列をその形式で正確に返す組み込みメソッドObject.entries(obj)を使用できます。
そのため、次のようにしてオブジェクトからマップを作成できます。
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]
]);
// now prices = { banana: 1, orange: 2, meat: 4 }
alert(prices.orange); // 2
Object.fromEntriesを使用して、Mapから通常のオブジェクトを取得できます。
例えば、データをMapに格納しますが、通常のオブジェクトを期待するサードパーティのコードに渡す必要があるとします。
このようにします。
let map = new Map();
map.set('banana', 1);
map.set('orange', 2);
map.set('meat', 4);
let obj = Object.fromEntries(map.entries()); // make a plain object (*)
// done!
// 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])– セットを作成します。iterableオブジェクト(通常は配列)が提供されている場合、その値をセットにコピーします。set.add(value)– 値を追加します。セット自体を返します。set.delete(value)– 値を削除します。呼び出し時に値が存在した場合はtrue、存在しなかった場合はfalseを返します。set.has(value)– 値がセットに存在する場合はtrue、存在しない場合はfalseを返します。set.clear()– セットからすべてを削除します。set.size– 要素数です。
主な機能は、同じ値でset.add(value)を繰り返し呼び出しても何も行われないことです。そのため、各値はSetに一度だけ表示されます。
例えば、訪問者が来ており、全員を覚えたいとします。しかし、繰り返し訪問しても重複してはなりません。訪問者は一度だけ「カウント」される必要があります。
Setはまさにそのためにあるものです。
let set = new Set();
let john = { name: "John" };
let pete = { name: "Pete" };
let mary = { name: "Mary" };
// visits, some users come multiple times
set.add(john);
set.add(pete);
set.add(mary);
set.add(john);
set.add(mary);
// set keeps only unique values
alert( set.size ); // 3
for (let user of set) {
alert(user.name); // John (then Pete and Mary)
}
Setの代替案としては、ユーザーの配列と、arr.findを使用して挿入ごとに重複をチェックするコードが考えられます。しかし、このメソッドは配列全体を走査してすべての要素をチェックするため、パフォーマンスははるかに悪くなります。Setは、一意性のチェックに関して内部的に最適化されています。
Setの反復処理
for..ofまたはforEachを使用してセットをループ処理できます。
let set = new Set(["oranges", "apples", "bananas"]);
for (let value of set) alert(value);
// the same with forEach:
set.forEach((value, valueAgain, set) => {
alert(value);
});
面白いことに、forEachに渡されるコールバック関数は、value、そして同じ値valueAgain、そしてターゲットオブジェクトの3つの引数を持っています。実際、同じ値が引数に2回出現します。
これは、forEachに渡されるコールバックが3つの引数を持つMapとの互換性のためです。確かに少し奇妙に見えます。しかし、これにより、特定のケースでMapをSetに簡単に置き換えたり、その逆を行ったりできる可能性があります。
イテレータ用のMapと同じメソッドもサポートされています。
set.keys()– 値のイテラブルオブジェクトを返します。set.values()–Mapとの互換性のために、set.keys()と同じです。set.entries()– エントリ[value, value]のイテラブルオブジェクトを返します。Mapとの互換性のために存在します。
概要
Map – キー付き値のコレクションです。
メソッドとプロパティ
new Map([iterable])– マップを作成します。初期化のために[key,value]ペアのiterable(例えば配列)をオプションで指定できます。map.set(key, value)– キーで値を格納します。マップ自体を返します。map.get(key)– キーで値を返します。キーがマップに存在しない場合はundefinedを返します。map.has(key)– キーが存在する場合はtrue、存在しない場合はfalseを返します。map.delete(key)– キーで要素を削除します。呼び出し時にキーが存在した場合はtrue、存在しなかった場合はfalseを返します。map.clear()– マップからすべてを削除します。map.size– 現在の要素数を返します。
通常のObjectとの違い
- あらゆるキー、オブジェクトをキーとして使用できます。
- 便利な追加メソッド、
sizeプロパティ。
Set – 一意の値のコレクションです。
メソッドとプロパティ
new Set([iterable])– セットを作成します。初期化のために値のiterable(例えば配列)をオプションで指定できます。set.add(value)– 値を追加します(valueが既に存在する場合は何も行いません)。Set 自体を返します。set.delete(value)– 値を削除します。呼び出し時に値が存在した場合はtrue、存在しなかった場合はfalseを返します。set.has(value)– 値がセットに存在する場合はtrue、存在しない場合はfalseを返します。set.clear()– セットからすべてを削除します。set.size– 要素数です。
Map と Set の反復処理は常に挿入順序で行われます。そのため、これらのコレクションが順序付けられていないとは言えませんが、要素の順序を変更したり、番号で要素に直接アクセスしたりすることはできません。
コメント
<code>タグを使用し、複数行の場合は<pre>タグで囲み、10行を超える場合はサンドボックス(plnkr、jsbin、codepen…)を使用してください。