組み込みの反復可能オブジェクト MapとSet

《 初回公開:2023/10/19 , 最終更新:未 》

【 目次 】

組み込みの反復可能オブジェクト

組み込みの反復可能オブジェクトには以下のものがある

  • Array
  • String
  • TypedArray
  • Map
  • Set
  • NodeList(およびその他の DOM コレクション)
  • arguments オブジェクト

代表的な組み込みの反復可能オブジェクトとしてMapとSetがあって、次にそれらについてみていく。

Map

Mapオブジェクトは辞書とかハッシュマップ、連想配列と呼ばれる機能を実現するもの。
キーと値のペアを保持するデータ構造。
配列がインデックス番号を使って値を取り出すのに対して、Mapはキーを使って値を取り出す。

似たようなネーミングに配列のmapメソッドがあるが、これと混同しないように注意。

JavaScriptではすべてのクラスのスーパークラスであるObjectクラスを使って連想配列を実現できるが、Objectクラスの連想配列はオブジェクトのプロパティとしての機能も兼用していてそれが故の不都合が生じてしまう。
Mapクラスはこの欠点を補う連想配列に特化したクラスと言える。

Mapオブジェクトの基本操作

Mapオブジェクトの作成

空のMapを作成
let map = new Map();

console.log(map);   // Map(0) {size: 0}
要素を指定してマップを作成

要素の指定には配列を使う
要素数分だけのキーと値のペアを指定。

let map = new Map([
    ["a", 1],
    ["b", 2],
    ["c", 3],
]);

console.log(map);   // Map(3) {'a' => 1, 'b' => 2, 'c' => 3}

要素の設定,登録・追加

要素を追加するにはsetメソッドを用いる。

空のMapを作成して要素を追加。
以下のコードは前記のコードと等価

let map = new Map();

map.set("a", 1);
map.set("b", 2);
map.set("c", 3);

console.log(map);   // Map(3) {'a' => 1, 'b' => 2, 'c' => 3}

setメソッドはMap オブジェクトを返すのでメソッドチェインが可能。
上記のコードは以下のように書き換える事も。

let map = new Map();

map.set("a", 1).set("b", 2).set("c", 3);
console.log(map);   // Map(3) {'a' => 1, 'b' => 2, 'c' => 3}

要素の値の修正・変更・上書き

既に登録済みの要素の値を変更するにはsetメソッドを用いて値を上書きする。

map.set("b", "新しい値");

console.log(map);   // {'a' => 1, 'b' => '新しい値', 'c' => 3}

要素の値の取り出し, 取得

要素の値を取得するにはgetメソッドの引数にキーを指定する

let value = map.get("a");

console.log(value); // 1

要素数の取得

要素数は読み取り専用のプロパティsizeに格納されている。

console.log(map.size);  // 3

要素の削除

deleteメソッドでキーを指定して要素を削除する。

let map = new Map([
    ["a", 1],
    ["b", 2],
    ["c", 3],
]);
let result = map.delete("a");
console.log(result, map);   // true Map(2) {'b' => 2, 'c' => 3}

result = map.delete("x");
console.log(result);    // false

deleteメソッドは削除に成功した場合trueを返す。
存在しないキーを指定するなどして削除に失敗した場合はfalseを返す。

すべての要素をいっぺんに削除するにはclearメソッドを使う。

let result = map.clear();
console.log(result, map);   // undefined Map(0) {size: 0}

clearメソッドの戻り値はundefined。

キーの存在確認

Mapに指定したキーの要素が格納されているかを確認するにはhasメソッドを使う。
hasメソッドは論理値を返す。

console.log(map.has("a"));  // true
console.log(map.has("x"));  // false

要素の反復処理

JavaScriptにはObjectクラスArrayクラス等、反復処理のためのkeys,values,entriesの3つのメソッドを備えているクラスがいくつか存在する。

keysメソッド

keysメソッドは要素のキーを挿入順に列挙する反復可能なオブジェクトを返す。
反復可能なのでfor...ofループを使って列挙可能

let map = new Map([
    ["a", 1],
    ["b", 2],
    ["c", 3],
]);

let keys = map.keys();
for (let key of keys) {
    console.log(key);
}
// a
// b
// c

valuesメソッド

同様にvaluesメソッドは、要素の値を挿入順に列挙する反復可能なオブジェクトを返す。

let values = map.values();
for (let value of values) {
    console.log(value);
}
// 1
// 2
// 3

entriesメソッド

entriesメソッドは要素のキーと値のペアの配列を挿入順に列挙する反復可能なオブジェクトを返す。

let entries = map.entries();
for (let entry of entries) {
    console.log(entry);
}
// ['a', 1]
// ['b', 2]
// ['c', 3]

entriesメソッドが返す反復可能なオブジェクトは要素のキーと値のペアの配列を返すので、配列から分割代入を使ってキーと値を個別に受け取る事もできる。

let entries = map.entries();
for (let [key, value] of entries) {
    console.log(`key=${key}, value=${value}`);
}
// key=a, value=1
// key=b, value=2
// key=c, value=3

Mapオブジェクト自身による反復処理

Mapオブジェクト自身が反復可能なのでentriesメソッドのように使う事もできる。

for (let [key, value] of map) {
    console.log(`key=${key}, value=${value}`);
}
// key=a, value=1
// key=b, value=2
// key=c, value=3

forEachメソッド - コールバック関数による反復処理

Mapには配列と同様にforEachメソッドという各要素をひとつづつ取り出して、各要素に対してコールバック関数を実行するメソッドが用意されている。
forEachメソッドの構文を示すと。

構文
map.forEach(callback(value, key, map)[, thisArg]);
引数
map
forEach メソッドを呼び出すMapオブジェクト。
callback
各要素に対して実行するコールバック関数。
コールバック関数の引数として
value
現在処理中の要素の値。
key
現在処理中の要素のキ-。
map
forEach メソッドを呼び出す元のMapオブジェクト。
thisArg (省略可能)
コールバック関数内で this として参照する値を指定。
戻り値
undefinedを返す。

コールバック関数にはアロー関数も指定できる。

map.forEach((value, key) => console.log(`map[${key}] = ${value}`));
// map[a] = 1
// map[b] = 2
// map[c] = 3

thisArgを指定すると指定したオブジェクトがコールバック関数内でthisとして参照できる。

let thisArg = { k: 5 };
map.forEach(function (value, key) {
    console.log(value * this.k);
}, thisArg);
// 5
// 10
// 15

コールバック関数にはアロー関数を指定した場合、引数thisArgは無視されるようだ。

JavaScriptのオブジェクトとMapオブジェクト

Mapクラスは連想配列のためのクラスであるが、もともとJavaScriptのオブジェクト自体が連想配列の機能を持っている。
これにはオブジェクトのプロパティの設定,取得を利用する。
以下のコードはその例である。

let map = new Object();
map["a"] = 1; map["b"] = 2; map["c"] = 3

console.log(map["a"], map["b"], map["c"])   // 1 2 3

では何故わざわざ連想配列専用のMapクラスが必要なのだろうか?
通常のオブジェクト(Plain Object)とMapの主な違いは

ChartGPTに説明してもらうと

キーのデータ型:

通常のオブジェクト: キーは文字列またはシンボル(Symbol)のみを使用できます。
Map: キーは任意のデータ型(プリミティブ型やオブジェクトも含む)を使用できます。つまり、文字列以外のキーも扱えます。

キーの順序:

通常のオブジェクト: キーの順序は挿入順ではなく、一般的には保証されません。異なる JavaScript エンジンや実行環境では順序が異なることがあります。
Map: キーと値のペアは挿入順序を保持します。イテレーション時にはエントリが挿入された順番にアクセスできます。

キーの比較:

通常のオブジェクト: キーの比較は文字列として行われ、異なるオブジェクトの同じ文字列表現でも異なるキーと見なされます。
Map: キーの比較は厳密な等価演算子(===)に基づいて行われ、同じデータ型かつ内容が同じでなければ同じキーとは見なされません。

補足すると、
JavaScriptのObjectクラスはprototypeプロパティを保持しており、そのprototypeプロパティに含まれるプロパティが余分に含まれている。
このprototypeプロパティに含まれるプロパティと後から追加するキーの間で名前の衝突が発生する恐れがある。
例えばprototypeプロパティにはtoStringメソッドが含まれており、toStringというキーを新たに登録するとObjectのtoStringメソッドとは別のものに上書きされてしまい混乱が生じてしまう事になる。

ObjectのtoStringメソッドを上書き

let obj = new Object();
console.log(obj["toString"]);   // toString() { [native code] }

obj["toString"]="xxx";
console.log(obj["toString"]);   // xxx

これに対してMapオブジェクトは初期状態では余分な要素は含まれていない事になり明解である。

また、MapオブジェクトもObjectクラスを継承しているのでオブジェクトの連想配列の機能が含まれている。
オブジェクトの連想配列の要素は、Mapの要素とは別個に管理される事になる。

let map = new Map([
    ["a", 1],
    ["b", 2],
    ["c", 3],
]);

map["x"] = 101; map["y"] = 102; map["z"] = 103;

// Mapの要素を列挙
for (let [key, value] of map) {
    console.log(`${key}: ${value}`);
}
// a: 1
// b: 2
// c: 3

// オブジェクトの列挙可能なプロパティを列挙
for (const [key, value] of Object.entries(map)) {
    console.log(`${key}: ${value}`);
}
// x: 101
// y: 102
// z: 103

オブジェクトの連想配列をMapオブジェクトに変換する事も可能で。

let obj = { "x": 101, "y": 102, "z": 103 };

let map = new Map(Object.entries(obj));
console.log(map);   // Map(3) {'x' => 101, 'y' => 102, 'z' => 103}

Set

Setオブジェクトは要素に重複する値がないことを保証したコレクション。
Setは英語で数学の集合の意。
数学の集合は1つの集合に同じ要素は入れない,集合の要素の順番は関係ない。

Setオブジェクトの基本操作

Setオブジェクトの作成

空のSetオブジェクトの作成

let set = new Set();
console.log(set);   // Set(0) {size: 0}

Setオブジェクトのコンストラクタは引数に反復可能オブジェクトを指定する事ができる。
配列も反復可能なので配列をSetオブジェクトに変換できる。
この時、重複する値の要素は取り除かれる。

let set = new Set(["a", "b", "c", "a", "b"]);
console.log(set);   // Set(3) {'a', 'b', 'c'}

オブジェクトの重複はどうなるかというと

let obj = { x: 10, y: 20 };
let obj2 = obj;

let set = new Set([obj, obj2]);
console.log(set);   // Set(1) {{…}} 

objとobj2は同じオブジェクトを参照しているので要素の数は1つ。

let obj = { x: 10, y: 20 };

let set = new Set([obj, { x: 10, y: 20 }]);
console.log(set);   // Set(2) {{…}, {…}}

しかし、オブジェクトの内容が同じでも別のオブジェクトを指定すれば重複とはみなされない。

要素の登録・追加

要素を追加するにはaddメソッドを用いる。
既にSetオブジェクトに含まれる重複した値を追加した場合は当然無視される。

let set = new Set();
set.add("a");
set.add("b");
set.add("c");

console.log(set);   // Set(3) {'a', 'b', 'c'}

// 重複した値を追加
set.add("a");
console.log(set);   // Set(3) {'a', 'b', 'c'}

addメソッドの戻り値はSetオブジェクト自身を返すのでメソッドチェインが可能。

set.add("a").add("b").add("c");

要素の削除

1つの要素の削除にはdeleteメソッドを用いる。
deleteメソッドは引数に削除したい要素を指定する。
削除したい要素がSetオブジェクトの要素であれば削除してtrueを返すが、Setオブジェクトの要素でない場合はfalseを返す。

すべての値を削除するためはclearメソッドを用いる。
clearメソッドメソッドの戻り値はundefined。

let set = new Set(["a", "b", "c"]);

// Setオブジェクトに含まれる要素を削除
console.log(set.delete("a"));   // true
// Setオブジェクトに含まれない要素を削除
console.log(set.delete("x"));   // false

// すべての要素を削除
// clearメソッドの戻り値はundefinedに
console.log(set.clear());   // undefined
// Setオブジェクトは空に
console.log(set);           // Set(0) {size: 0}

要素数の取得

Mapと同様に要素数は読み取り専用のプロパティsizeに格納されている。

    let set = new Set(["a", "b", "c"]);
    console.log(set.size);  // 3

要素の存在確認

Mapと同様に、Setに指定した要素の要素が格納されているかを確認するにはhasメソッドを使う。
hasメソッドは論理値を返す。

console.log(set.has("a"));  // true
console.log(set.has("x"));  // false

要素の取り出し, 取得 の反復処理

挿入順で反復処理をおこなう事で要素の取り出し, 取得する。 数学の集合には要素の順番は無いが、Setオブジェクトは挿入順となる。
Mapと同様にkeys、values、entriesメソッドおよびforEachメソッドが使える。

valuesメソッド

valuesメソッドはSetオブジェクトに含まれる要素を、挿入順に列挙する反復可能なオブジェクトを返す。
反復可能なのでfor...ofループを使って列挙可能

let set = new Set(["a", "b", "c"]);

for (let element of set.values()) {
    console.log(element);
}
// a
// b
// c

keysメソッド

Mapと異なりSetにはキーが存在しないので、keysメソッドはキーでは無く要素の値を出力する。
keysメソッドはvaluesメソッドの別名になっていて、
valuesメソッドがあればkeysメソッドは必要無くて使うことも無いが、Mapとの整合性をとるために存在している。

let set = new Set(["a", "b", "c"]);

for (let element of set.keys()) {
    console.log(element);
}
// a
// b
// c

entriesメソッド

entriesメソッドもkeysメソッドと同様,本来は必要無くて使うことも無いように思うが、
キーが存在しないので要素の値,同じ値を2つ含んだ配列を挿入順に列挙する反復可能なオブジェクトを返す。

let set = new Set(["a", "b", "c"]);

for (let entry of set.entries()) {
    console.log(entry);
}
// ['a', 'a']
// ['b', 'b']
// ['c', 'c']

Setオブジェクト自身による反復処理

Map同様、SetオブジェクトもSetオブジェクト自身が反復可能なのでvaluesメソッドのように使う事もできる。

for (let value of set) {
    console.log(value);
}
// a
// b
// c

forEachメソッド - コールバック関数による反復処理

Setには配列やMapと同様に、forEachメソッドという各要素をひとつづつ取り出して各要素に対してコールバック関数を実行するメソッドが用意されている。
forEachメソッドの構文を示すと。

構文
set.forEach(callback(value, key, set)[, thisArg]);
引数
set
forEach メソッドを呼び出すSetオブジェクト自体。
callback
各要素に対して実行するコールバック関数。
コールバック関数の引数として
value
現在処理中の要素の値。
key
Setオブジェクトにはキーが存在しないので現在処理中の要素の値つまりvalueと同じ値。
set
forEach メソッドを呼び出す元のSetオブジェクト。
thisArg (省略可能)
コールバック関数内で this として参照する値を指定。
戻り値
undefinedを返す。

コールバック関数にはアロー関数も指定できる。

let set = new Set(["a", "b", "c"]);

set.forEach((value) => console.log(value));
// a
// b
// c

thisArgを指定すると指定したオブジェクトがコールバック関数内でthisとして参照できる。

let set = new Set(["a", "b", "c"]);

let thisArg = { k: "element_" };
set.forEach(function (value) {
    console.log(this.k + value);
}, thisArg);
// element_a
// element_b
// element_c

コールバック関数にはアロー関数を指定した場合、thisArgの指定は無視されるようだ。

let set = new Set(["a", "b", "c"]);

let thisArg = { k: "element_" };
set.forEach((value) => console.log(this.k + value));
// undefineda
// undefinedb
// undefinedc

javascriptのコレクションクラス

MapやSetは反復可能オブジェクトであるがコレクションクラスに分類される。

コレクションクラスとはデータを効率的に管理するためのクラスで、データを集めたり操作したりするための特別なデータ構造を持つ。
データを格納するためのプロパティや、データを操作するためのメソッドを備えている。

代表的なjavascriptのコレクションにはMapやSet,配列(Array)が挙げられる。
オブジェクト(Plain Object)もキー(プロパティ)と値のペアを格納できるため、広い意味ではコレクションクラスと言えなくもない。

コレクションクラスが必ずしも反復可能であるとは言えないが、コレクションクラスは反復可能である可能性が高い。

ページのトップへ戻る