TypeScript実践力を身につける!コードスニペットで学ぶ型付けとインターフェース
JavaScriptにおいて、複数の値を扱いたい場合に思いつくのが「配列」です。しかしながら、配列では項目に名前をつけたいときもあるでしょう。
そんな時に使うのが「Mapオブジェクト(連想配列)」です。
また、JavaScriptの配列を扱うために便利なメソッドとして用意されているのが「mapメソッド」。
両方とも「map」とついているので、プログラミング初心者のなかには混同してしまう人もいるでしょう。
本記事では、mapメソッドとMapオブジェクトの違いや使い方も含め、詳しく解説していきます。
1.Mapオブジェクトとは?
JavaScriptにおけるMapオブジェクトは、キー(key)とそれに対応する値(value)を対応させて保持するオブジェクトです。
配列やJavaScriptのオブジェクトとは異なり、キーとなる値に対して文字列や数値、真偽値(Boolean)などを自由に設定できるというメリットを持ちます。
const mapObject= new Map([
['first', 1],
[2, 'second'],
[true, 'this is a true.'],
[false, 'this is a false']
]);
console.log(`first: ${mapObject.get('first')}`);
// -> 「first: 1」と出力される
console.log(`true: ${mapObject.get(true)}`);
// -> 「true: this is a true.」と出力される
Mapオブジェクトは、ES2015(ES6)で新たに登場した比較的新しい機能ですが、JavaScriptの基本機能として定義されています。
Mapオブジェクトは比較的新しい機能であるため、IE11では動作しないという問題がありました。とはいえ、Windows10向けのIE11は2022年6月をもってサポートが終了しましたので、もう気にする必要はないでしょう。
①Setオブジェクトとの違い
Mapオブジェクトと同じく、ES2015(ES6)に同時に採用された機能が「Setオブジェクト」です。SetもMapと同様に、複数の値を扱える便利な機能です。
しかしながら、Setオブジェクトはキーを持たず値の一覧を保持するだけであるため、JavaScriptにおけるArray(配列)と同じような使い方をします。
SetオブジェクトとArrayの大きな違いは、「値の重複を許さない」という点です。
例えば、Setオブジェクトの場合には次のような配列は許可されていません。
const setArr = new Set(['val1', 'val2', 'val1', 'val3']);
console.log(setArr);
// -> 「{"val1","val2","val3"}」と出力される
この特性は、Mapオブジェクトにおけるキーの特性と似ています。Mapオブジェクトのキーも、重複した値を保持できません。
同じキーの値を設定した場合、あとから指定した値で上書きされてしまいます。
const mapObject= new Map([
['first', 1],
[2, 'second'],
[true, 'this is a true.'],
[false, 'this is a false'],
['first', 2] // <- 同じキー値に違う値を指定
]);
console.log(`first: ${mapObject.get('first')}`);
// -> 「first: 2」が出力される
このように、MapオブジェクトとSetオブジェクトは異なる使い方をしますが、内部的な挙動には共通事項が多いことがわかります。
どちらもうまく使いこなせるようになると良いでしょう。
2.Mapオブジェクトの基本的な使い方
Mapオブジェクトの概要を理解したところで、Mapオブジェクトの使い方をマスターしていきましょう。
①オブジェクトの生成と初期化
Mapオブジェクトを生成するには、「new」キーワードを利用します。
const mapObj = new Map();
また、値を指定して初期化をする場合には以下のようなコードを書きます。
const mapObj = new Map();
また、値を指定して初期化をする場合には以下のようなコードを書きます。
const mapObj = new Map([
['key1', 1],
['key2', 2]
]);
このように、Mapオブジェクトを初期化したい場合にはキーと値をもつ配列を指定します。
配列が入れ子になってしまい少し面倒ですが、Mapオブジェクトを使いこなすうえでは非常に重要なのでしっかり覚えておきましょう。
②要素の追加と取得
Mapオブジェクトへ要素を追加するには、「set」メソッドを、要素を取得する場合には「get」メソッドを利用します。
// オブジェクトを生成
const mapObj = new Map();
// setを利用して値をセット
mapObj.set('key1', 'value1');
// getを利用して値を取得
console.log(mapObj.get('key1'));
// -> 「value1」が出力される
③データの削除
Mapオブジェクトからデータを削除する場合には、「delete」メソッドを使用します。
// オブジェクトを生成
const mapObj = new Map([
['key1', 'value1'],
['key2', 'value2']
]);
// deleteを利用して値を削除
mapObj.delete('key1');
// getを利用して値を取得
console.log(mapObj.get('key1'));
// -> 中身が消えているため「undefined」が出力される
console.log(mapObj.get('key2'));
// -> key2の内容は残っているため「value2」が出力される
3.Mapオブジェクトの反復処理
Mapオブジェクトは複数の値を保持できるので、中身を反復(ループ)して利用するプログラムを作成することも多いでしょう。
ここでは、Mapオブジェクトを用いた反復処理の方法を紹介します。
①forEachメソッド
Mapオブジェクトの内部要素をループして処理を実行したい場合には「forEachメソッド」を利用できます。
const objectMap = new Map([
['key1', 'value1'],
['key2', 'value2'],
['key3', 'value3']
]);
objectMap.forEach((value, key) => {
// 第一引数にキーが、第二引数に値が渡される
console.log(`The value of ${key} is ${value}`);
});
この場合、以下のように出力されます。
The value of key1 is value1
The value of key2 is value2
The value of key3 is value3
このように、forEachメソッドの引数に関数を指定することで反復処理を実装できるのです。
この、開発者が自由に記述できる関数を「コールバック関数」と呼びます。
forEachメソッドは、コールバック関数の中に記述した処理をMapオブジェクトのすべての要素に対して実行します。
forEachメソッドについてさらに詳しく知りたい方は、以下も参考にしてみてください。
②keysメソッド
Mapオブジェクトのkeysメソッド利用することで、キーの一覧を取得できます。
先ほどのforEachメソッドを利用するコードは、以下のように書き換えることもできます。
for (let key of objectMap.keys()) {
console.log(`The value of ${key} is ${objectMap.get(key)}`);
}
このときの出力結果は以下の通りです。
The value of key1 is value1
The value of key2 is value2
The value of key3 is value3
③valuesメソッド
keysメソッドがキーの一覧を取得できるのに対し、値の一覧を取得する場合にはvaluesメソッドを利用します。
for (let value of objectMap.values()) {
console.log(`value is ${value}`);
}
これを実行すると以下のように出力されます。
value is value1
value is value2
value is value3
valuesメソッドの注意点
注意点として、valuesメソッドで取得した値から、マップのキーを取得することは難しいことを覚えておきましょう。
Mapオブジェクトに格納されるキーは重複できないため、キーから値を取得することはできますが、複数のキーに対して同じ値は代入できます。
そのため、「値からキーを特定する」ことができない場合もあります。
const valuesMap = new Map([
['key1', 2],
['key2', 3],
['key3', 2]
]);
この場合、「2」が重複しているため、「2」という値からキー値を取得しようとすると「key1」および「key3」の選択肢が発生します。
valuesメソッドを利用する際には、キーを取得できない前提で考えると良いでしょう。
4.mapメソッドとは?
JavaScriptのMapオブジェクトの使い方をマスターしたところで、次は配列のmapメソッドについて解説していきます。
どちらも「map」という単語を利用していますが、これらの挙動は異なります。
Mapオブジェクトはキーと値の情報を格納するために利用しますが、mapメソッドの主な目的は「配列を違う形に変換する」ことです。
5.mapメソッドの使い方
それでは、mapメソッドの使い方から解説します。
①配列を繰り返し処理する方法
// 操作する配列
const arr = ['value1', 'value2', 'value3'];
const converted = arr.map((value, idx) => {
return `${idx} is ${value}`
});
console.log(converted);
このとき、convertedのオブジェクトは以下のように変換されています。
["0 is value1","1 is value2","2 is value3"]
②配列の中身の型を変換する
先ほどの例では文字列の中身を変更しましたが、mapメソッド主に配列の形式を変換するために利用します。
const toMap = arr.map((value, idx) => {
return {
'key': idx,
'value': value
};
});
console.log(toMap);
このときのtoMapの中身を確認すると、オブジェクトの配列となっていることがわかります。
[
{
"key": 0,
"value":"value1"
}, {
"key": 1,
"value": "value2"
}, {
"key": 2,
"value": "value3"
}
]
このように、mapメソッドを利用することで、配列の中身を別な型に変換したり、オブジェクトの配列から特定の内容のみを抽出したりすることも可能です。
6.他のメソッドとの違いと使い分け
Mapオブジェクトと同様に、配列には便利なメソッドが用意されています。
その場に応じてうまく利用しましょう。
①forEachメソッド
forEachメソッドは、配列を単純に反復処理したい場合に利用します。
const objectMap = new Map([
['key1', 'value1'],
['key2', 'value2'],
['key3', 'value3']
]);
objectMap.forEach((value, key) => {
// 第一引数にキーが、第二引数に値が渡される
console.log(`The value of ${key} is ${value}`);
});
mapメソッドもforEachメソッドも、配列の反復処理が可能であるため同様の処理ができますが、forEachメソッドはmapメソッドと異なりコールバック関数は戻り値を取りません。
そのため、
・配列の変換が必要なら「mapメソッド」を使う
・配列の変換が不要なら「forEachメソッド」を使う
と覚えましょう。
②filterメソッド
filterメソッドは、配列から特定の条件を持つ要素を取得するために利用します。配列に対して検索のような処理を実現できると覚えましょう。
// 対象の配列
const target = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
// filter関数を利用して偶数を抽出
const evens = target.filter((value) => {
// 2で割った余りが0かを返却
return value % 2 === 0
});
console.log(evens);
// -> [2,4,6,8,10] が出力される
③reduceメソッド
reduceメソッドは、配列のすべての内容を利用して単一の計算結果を求めたい場合に利用します。
総和や総乗、行列計算といった数値計算に利用できると覚えましょう。
今回は、例として配列のすべての要素を足し算して総和を求める場合を紹介します。
// 対象の配列
const target = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const sum = target.reduce((prev, current, idx) => {
console.log(`${idx}: prev is ${prev}, current is ${current}`);
// 合計額に、現在の値を加算する
return prev + current;
});
console.log(sum);
このとき、最終的なsumの値は以下のように出力されます。
55
また、reduceメソッド内のコールバック関数の実行内容も見てみましょう。
合計値を表示する前には、以下のように出力されているはずです。
1: prev is 1, current is 2
2: prev is 3, current is 3
3: prev is 6, current is 4
4: prev is 10, current is 5
5: prev is 15, current is 6
6: prev is 21, current is 7
7: prev is 28, current is 8
8: prev is 36, current is 9
9: prev is 45, current is 10
出力された結果をみると、
・prev(第一引数)にはそれまでの合計値
・currentには現在の値
・idxには配列のインデックス値
が渡されることがわかります。
reduceメソッドは配列の0番目ではなく、1番目の値から処理がスタートされることに注意しましょう。この場合、reduceメソッドの1回目の処理には、prevに0番目の値である「1」が渡されて処理がスタートしています。
もし、すべての配列に対して処理を行わせたい場合には以下のように書き換えましょう。
const sum = target.reduce((prev, current, idx) => {
console.log(`${idx}: prev is ${prev}, current is ${current}`);
// 合計額に、現在の値を加算する
return prev + current;
}, 0); // <- 初期値として0を指定する
あらためて実行すると、以下のように出力されます。
0: prev is 0, current is 1
1: prev is 1, current is 2
2: prev is 3, current is 3
3: prev is 6, current is 4
4: prev is 10, current is 5
5: prev is 15, current is 6
6: prev is 21, current is 7
7: prev is 28, current is 8
8: prev is 36, current is 9
9: prev is 45, current is 10
さきほどの例と異なり、引数「prev」に0が渡され0番目の配列要素の処理が実行されていることがわかります。
④ flatメソッド
mapメソッドと同様によく使われるのが「flat」メソッドです。
flatメソッドは、mapメソッドの処理中で多次元配列となってしまった配列をフラットに変更してくれます。
例として、複数の文章を単語ごとに区切って格納するようなプログラムを考えてみましょう。
const messages = ['This is a pen', 'Golden', 'Silver Moon'];
const words = messages.map((value, idx) => {
// 空白で区切ってリスト化
return value.split(" ")
});
console.log(words);
実行すると、wordsには以下のように多次元配列で格納されてしまっています。
[
['This', 'is', 'a', 'pen'],
['Golden'],
['Silver', 'Moon']
]
これを以下のように書き換えることで、一次元の配列へ変換されます。
const words = messages.map((value, idx) => {
// 空白で区切ってリスト化
return value.split(" ")
}).flat(); // <- flat()を呼び出し
再度wordsの中身を確認してみると、以下のようにフラットな配列へ変換されていることがわかります。
["This","is","a","pen","Golden","Silver","Moon"]
このように、flatメソッドとmapメソッドを一緒に利用することで、より柔軟なプログラムができると覚えましょう。
⑤ flatMapメソッド
flatMapメソッドは、さらに新しい規格であるES2019で制定されました。
「map + flat」の組み合わせを一度にやってくれる便利なメソッドです。
const words = messages.flatMap((value, idx) => {
return value.split(" ");
});
このように、JavaScriptには新しい機能がどんどん追加されています。
ブラウザごとに対応状況が異なるため注意は必要ですが、これらの新機能を活用することで、さらに効率よくプログラミングを進められるでしょう。