Map
)Map<K,V>
Map<K,V>.prototype
:単一エントリの処理Map<K,V>.prototype
:すべてのエントリの処理Map<K,V>.prototype
:反復処理とループ.size
を持ち、配列は.length
を持つのか?ES6以前は、JavaScriptには辞書のためのデータ構造がなく、文字列から任意の値への辞書としてオブジェクトを(不適切に)使用していました。ES6では、任意の値から任意の値への辞書であるマップが導入されました。
Map
のインスタンスは、キーを値にマッピングします。単一のキーと値のマッピングは、_エントリ_と呼ばれます。
マップを作成するには、3つの一般的な方法があります。
まず、パラメータなしでコンストラクタを使用して、空のマップを作成できます。
const emptyMap = new Map();
.equal(emptyMap.size, 0); assert
次に、キーと値の「ペア」(2つの要素を持つ配列)に対する反復可能オブジェクト(例:配列)をコンストラクタに渡すことができます。
const map = new Map([
1, 'one'],
[2, 'two'],
[3, 'three'], // trailing comma is ignored
[; ])
3番目に、.set()
メソッドはマップにエントリを追加し、チェーン可能です。
const map = new Map()
.set(1, 'one')
.set(2, 'two')
.set(3, 'three');
後述するように、マップはキーと値のペアに対する反復可能オブジェクトでもあります。そのため、コンストラクタを使用してマップのコピーを作成できます。そのコピーは_浅い_コピーです。キーと値は同じで、複製されません。
const original = new Map()
.set(false, 'no')
.set(true, 'yes');
const copy = new Map(original);
.deepEqual(original, copy); assert
.set()
と.get()
は、(キーが与えられた場合の)値の書き込みと読み取りに使用します。
const map = new Map();
.set('foo', 123);
map
.equal(map.get('foo'), 123);
assert// Unknown key:
.equal(map.get('bar'), undefined);
assert// Use the default value '' if an entry is missing:
.equal(map.get('bar') ?? '', ''); assert
.has()
は、マップに指定されたキーを持つエントリがあるかどうかを確認します。 .delete()
はエントリを削除します。
const map = new Map([['foo', 123]]);
.equal(map.has('foo'), true);
assert.equal(map.delete('foo'), true)
assert.equal(map.has('foo'), false) assert
.size
には、マップ内のエントリ数が含まれています。 .clear()
は、マップのすべてのエントリを削除します。
const map = new Map()
.set('foo', true)
.set('bar', false)
;
.equal(map.size, 2)
assert.clear();
map.equal(map.size, 0) assert
.keys()
は、マップのキーに対する反復可能オブジェクトを返します。
const map = new Map()
.set(false, 'no')
.set(true, 'yes')
;
for (const key of map.keys()) {
console.log(key);
}// Output:
// false
// true
Array.from()
を使用して、.keys()
から返された反復可能オブジェクトを配列に変換します。
.deepEqual(
assertArray.from(map.keys()),
false, true]); [
.values()
は.keys()
と同様に機能しますが、キーの代わりに値に対して機能します。
.entries()
は、マップのエントリに対する反復可能オブジェクトを返します。
const map = new Map()
.set(false, 'no')
.set(true, 'yes')
;
for (const entry of map.entries()) {
console.log(entry);
}// Output:
// [false, 'no']
// [true, 'yes']
Array.from()
は、.entries()
から返された反復可能オブジェクトを配列に変換します。
.deepEqual(
assertArray.from(map.entries()),
false, 'no'], [true, 'yes']]); [[
マップインスタンスは、エントリに対する反復可能オブジェクトでもあります。次のコードでは、分割代入を使用して、`map`のキーと値にアクセスしています。
for (const [key, value] of map) {
console.log(key, value);
}// Output:
// false, 'no'
// true, 'yes'
マップは、エントリが作成された順序を記録し、エントリ、キー、または値をリストする際にその順序を尊重します。
const map1 = new Map([
'a', 1],
['b', 2],
[;
]).deepEqual(
assertArray.from(map1.keys()), ['a', 'b']);
const map2 = new Map([
'b', 2],
['a', 1],
[;
]).deepEqual(
assertArray.from(map2.keys()), ['b', 'a']);
マップがキーとして文字列とシンボルのみを使用している限り、(Object.fromEntries()
を介して)オブジェクトに変換できます。
const map = new Map([
'a', 1],
['b', 2],
[;
])const obj = Object.fromEntries(map);
.deepEqual(
assert, {a: 1, b: 2}); obj
また、(Object.entries()
を介して)文字列またはシンボルキーを持つオブジェクトをマップに変換することもできます。
const obj = {
a: 1,
b: 2,
;
}const map = new Map(Object.entries(obj));
.deepEqual(
assert, new Map([['a', 1], ['b', 2]])); map
`countChars()`は、文字を出現回数にマッピングするマップを返します。
function countChars(chars) {
const charCounts = new Map();
for (let ch of chars) {
= ch.toLowerCase();
ch const prevCount = charCounts.get(ch) ?? 0;
.set(ch, prevCount+1);
charCounts
}return charCounts;
}
const result = countChars('AaBccc');
.deepEqual(
assertArray.from(result),
['a', 2],
['b', 1],
['c', 3],
[
]; )
任意の値がキーになることができ、オブジェクトでも可能です。
const map = new Map();
const KEY1 = {};
const KEY2 = {};
.set(KEY1, 'hello');
map.set(KEY2, 'world');
map
.equal(map.get(KEY1), 'hello');
assert.equal(map.get(KEY2), 'world'); assert
ほとんどのマップ操作では、値がキーのいずれかと等しいかどうかを確認する必要があります。内部操作SameValueZeroを介して行います。これは`===`のように機能しますが、`NaN`は自身と等しいとみなします。
結果として、他の値と同様に、マップで`NaN`をキーとして使用できます。
> const map = new Map();
> map.set(NaN, 123);
> map.get(NaN)123
異なるオブジェクトは、常に異なるとみなされます。これは変更できないものです(ただし、キーの等価性の設定は、TC39の長期ロードマップにあります)。
> new Map().set({}, 1).set({}, 2).size2
配列を`.map()`および`.filter()`できますが、マップにはそのような操作はありません。解決策は次のとおりです。
次のマップを使用して、その仕組みを説明します。
const originalMap = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
`originalMap`のマッピング
const mappedMap = new Map( // step 3
Array.from(originalMap) // step 1
.map(([k, v]) => [k * 2, '_' + v]) // step 2
;
).deepEqual(
assertArray.from(mappedMap),
2,'_a'], [4,'_b'], [6,'_c']]); [[
`originalMap`のフィルタリング
const filteredMap = new Map( // step 3
Array.from(originalMap) // step 1
.filter(([k, v]) => k < 3) // step 2
;
).deepEqual(Array.from(filteredMap),
assert1,'a'], [2,'b']]); [[
`Array.from()`は、任意の反復可能オブジェクトを配列に変換します。
マップを結合するためのメソッドがないため、前のセクションと同様の回避策を使用する必要があります。
次の2つのマップを結合してみましょう。
const map1 = new Map()
.set(1, '1a')
.set(2, '1b')
.set(3, '1c')
;
const map2 = new Map()
.set(2, '2b')
.set(3, '2c')
.set(4, '2d')
;
`map1`と`map2`を結合するために、新しい配列を作成し、`map1`と`map2`のエントリ(キーと値のペア)を(反復を介して)スプレッド(`...`)します。次に、配列をマップに戻します。これらはすべて行Aで行われます。
const combinedMap = new Map([...map1, ...map2]); // (A)
.deepEqual(
assertArray.from(combinedMap), // convert to Array for comparison
1, '1a' ],
[ [ 2, '2b' ],
[ 3, '2c' ],
[ 4, '2d' ] ]
[ ; )
演習:2つのマップの結合
exercises/maps/combine_maps_test.mjs
注:簡潔にするために、すべてのキーが同じ型`K`を持ち、すべての値が同じ型`V`を持つと仮定しています。
`new Map<K, V>(entries?: Iterable<[K, V]>)` [ES6]
パラメータ`entries`を指定しないと、空のマップが作成されます。[key, value]ペアに対する反復可能オブジェクトを指定すると、それらのペアがエントリとしてマップに追加されます。例えば
const map = new Map([
1, 'one' ],
[ 2, 'two' ],
[ 3, 'three' ], // trailing comma is ignored
[ ; ])
`.get(key: K): V` [ES6]
このマップで`key`がマッピングされている`value`を返します。このマップにキー`key`がない場合、`undefined`が返されます。
const map = new Map([[1, 'one'], [2, 'two']]);
.equal(map.get(1), 'one');
assert.equal(map.get(5), undefined); assert
`.set(key: K, value: V): this` [ES6]
指定されたキーを指定された値にマッピングします。キーが`key`であるエントリが既に存在する場合、更新されます。そうでない場合は、新しいエントリが作成されます。このメソッドは`this`を返すため、チェーンすることができます。
const map = new Map([[1, 'one'], [2, 'two']]);
.set(1, 'ONE!')
map.set(3, 'THREE!');
.deepEqual(
assertArray.from(map.entries()),
1, 'ONE!'], [2, 'two'], [3, 'THREE!']]); [[
`.has(key: K): boolean` [ES6]
指定されたキーがこのマップに存在するかどうかを返します。
const map = new Map([[1, 'one'], [2, 'two']]);
.equal(map.has(1), true); // key exists
assert.equal(map.has(5), false); // key does not exist assert
`.delete(key: K): boolean` [ES6]
キーが`key`であるエントリが存在する場合、削除され、`true`が返されます。そうでない場合は、何も起こらず、`false`が返されます。
const map = new Map([[1, 'one'], [2, 'two']]);
.equal(map.delete(1), true);
assert.equal(map.delete(5), false); // nothing happens
assert.deepEqual(
assertArray.from(map.entries()),
2, 'two']]); [[
`get .size: number` [ES6]
このマップにいくつのエントリがあるかを返します。
const map = new Map([[1, 'one'], [2, 'two']]);
.equal(map.size, 2); assert
`.clear(): void` [ES6]
このマップからすべてのエントリを削除します。
const map = new Map([[1, 'one'], [2, 'two']]);
.equal(map.size, 2);
assert.clear();
map.equal(map.size, 0); assert
反復処理とループはどちらも、エントリがマップに追加された順序で行われます。
`.entries(): Iterable<[K,V]>` [ES6]
このマップの各エントリについて、1つの[key, value]ペアを持つ反復可能オブジェクトを返します。ペアは長さ2の配列です。
const map = new Map([[1, 'one'], [2, 'two']]);
for (const entry of map.entries()) {
console.log(entry);
}// Output:
// [1, 'one']
// [2, 'two']
`.forEach(callback: (value: V, key: K, theMap: Map<K,V>) => void, thisArg?: any): void` [ES6]
最初のパラメータは、このマップの各エントリに対して1回呼び出されるコールバックです。`thisArg`が指定されている場合、各呼び出しに対して`this`がそれに設定されます。そうでない場合、`this`は`undefined`に設定されます。
const map = new Map([[1, 'one'], [2, 'two']]);
.forEach((value, key) => console.log(value, key));
map// Output:
// 'one', 1
// 'two', 2
`.keys(): Iterable<K>` [ES6]
このマップのすべてのキーに対する反復可能オブジェクトを返します。
const map = new Map([[1, 'one'], [2, 'two']]);
for (const key of map.keys()) {
console.log(key);
}// Output:
// 1
// 2
`.values(): Iterable<V>` [ES6]
このマップのすべての値に対する反復可能オブジェクトを返します。
const map = new Map([[1, 'one'], [2, 'two']]);
for (const value of map.values()) {
console.log(value);
}// Output:
// 'one'
// 'two'
`[Symbol.iterator](): Iterable<[K,V]>` [ES6]
マップを反復処理するデフォルトの方法です。`.entries()`と同じです。
const map = new Map([[1, 'one'], [2, 'two']]);
for (const [key, value] of map) {
console.log(key, value);
}// Output:
// 1, 'one'
// 2, 'two'
文字列でもシンボルでもないキーを持つ辞書のようなデータ構造が必要な場合は、選択肢がありません。マップを使用する必要があります。
ただし、キーが文字列またはシンボルの場合は、オブジェクトを使用するかどうかを決定する必要があります。大まかな一般的なガイドラインは次のとおりです。
(開発時に既知の)キーの固定セットはありますか?
その場合は、オブジェクト`obj`を使用し、固定キーを介して値にアクセスします。
const value = obj.key;
キーのセットは実行時に変更できますか?
その場合は、マップ`map`を使用し、変数に格納されているキーを介して値にアクセスします。
const theKey = 123;
.get(theKey); map
通常、マップキーは値によって比較されるようにします(2つのキーは、同じ内容であれば等しいとみなされます)。これにはオブジェクトは含まれません。ただし、オブジェクトをキーとして使用するユースケースが1つあります。オブジェクトに外部からデータを添付する場合です。ただし、そのユースケースはWeakMapの方が適しています。WeakMapでは、エントリによってキーがガベージコレクトされるのを防ぐことはありません(詳細は、次の章を参照してください)。
原則として、マップは順序付けされていません。エントリを順序付ける主な理由は、エントリ、キー、または値をリストする操作を決定論的にするためです。これは、たとえばテストに役立ちます。
JavaScriptでは、インデックス付きシーケンス(配列や文字列など)は`.length`を持ち、インデックスなしコレクション(マップやセットなど)は`.size`を持ちます。
クイズ
クイズアプリを参照してください。