これまで、以下の複合データ構造について学習してきました。
- オブジェクトは、キー付きコレクションの格納に使用されます。
- 配列は、順序付けられたコレクションの格納に使用されます。
しかし、それは現実世界では不十分です。そのため、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…)を使用してください。