JavaScript入門 コレクション Set、Map、WeakMapの解説

最終更新日

Set

ES6で追加されたSetは、一意の値を格納するためのコレクションです。Setには、重複した値を保持でき前せん。重複した値を登録しようとした場合には、その値は無視されます。コレクション内に追加されません。

Setの初期化

構文:Setの初期化

空のSetオブジェクトを作成する場合
const Setオブジェクト = new Set();
反復可能オブジェクトからSetオブジェクトを作成する場合
const Setオブジェクト = new Set( 反復可能オブジェクト )

Setでは、インスタンス化の際に配列などの反復可能オブジェクトを引数に渡すことで、それらの要素を含むSetオブジェクトを作成できます。反復可能オブジェクトをとは、Set、Map、配列のような反復処理が可能なオブジェクトのことです。

配列からSetオブジェクトを作成

const convertedSet = new Set( [1,2,3] );
console.log( convertedSet );
>Set { 1, 2, 3}

Setのメソッド

add 値の追加

addメソッドを使うと、Setオブジェクトに対して値を追加できます。プリミティブ型、オブジェクトにかかわらず、どのような値でも追加可能です。

Setオブジェクトに値を追加

const fruits = new Set();
fruits.add( "apple" );       //"apple"をfruits(Setオブジェクト)に追加
fruits.add( "orange" );
fruits.add( "orange" );      //重複した値を追加
console.log( fruits);
> Set { "apple" , "orange" } //登録された値は一意になる

delete コレクション内の値の削除

addメソッドを使うと、Setオブジェクト内から値を削除できます。

Setオブジェクトに値を削除

const fruits = new Set( [ "apple" , "orange" ] );
fruits.delete( "orange" );  //"orange"をfruitsから削除
console.log( fruits);
> Set { "apple" } 

clear コレクション内の値をすべて削除

clear メソッドを使うと、Setオブジェクトが保持する値をすべて削除します。

clear オブジェクトが保持する値をすべて削除

const fruits = new Set( [ "apple" , "orange" ] );
fruits.clear ();
console.log( fruits);
> Set { } 

has コレクション内に値が存在するか確認

has メソッドは、Setオブジェクト内に一致する値が存在するかを確認します。

has オブジェクト内の存在確認

const fruits = new Set( [ "apple" , "orange" ] );
console.log( fruits.has( "orange" ) );  //Setオブジェクトに含まれる値の場合
>true
console.log( fruits.has( "banana" ) );  //Setオブジェクトに含まれない値の場合
>false

コールバック関数を引数に取るメソッド

コールバック関数を引数に取るMapのメソッドもforEachです

forEach コレクションの要素をループ処理

setのforEachメソッドは、配列のforEachと同様、コールバック関数を使ってループ処理を行います。

構文:forEachの記法

Setオブジェクト.forEach( value, sameValue, set ){
    /* Setの各要素を使った処理 */
} [, _this]);
  • value:Setの値が1つずつ渡されてきます。
  • sameValue :valueと同じ値が渡されます。
  • map :Setオブジェクト自体が渡されます。
  • _this :コールバック関数内のthisの参照先を設定します。

Setの場合、コールバック関数の第1引数valueと第2引数sameValueには、それぞれ同じ値が渡されます。

Setのループ

const set = new Set( [ "値1" , "値2" ] );
set.forEach( ( value, sameValue, set ) => {
    console.log( `value:[${value}], sameValue:[${sameValue}], set:`, set);
});
実行結果
実行結果

Setのプロパティ

size コレクションの長さを取得

Setはsizeプロパティに長さが格納されています。

Setオブジェクトの長さを確認

const fruits = new Set( [ "apple" , "orange" ] );
console.log( fruits.size );
> 2

Setオブジェクトの利用ケース

配列に対して重複した値を取り除くような処理を記述するのは、意外と面倒です。しかし、Setを利用すると簡単に配列から重複値を取り除くことが出来ます。

配列から重複値を取り除く処理

const fruits = new Set( [ "apple" , "orange" , "banana" , "orange" , "apple" ] ); //重複値を含む配列
const fruitsSet = new Set( fruits );  //Setオブジェクトへ変換することで、重複値が取り除かれる
fruits = Array.from( fruitsSet );  //再度、配列に変換
console.log( fruits );
>[ "apple","orange","banana" ]

Map

ES6で追加されたMapは、キーと値を対で保持するコレクションです。ES5までは、オブジェクト{ }を使って、キーと値を対で管理していました。ES6以降では、Mapを使うことで、さらに便利に値を管理できます。

Mapの初期化

const emptyMap = new Map();   //空のMapオブジェクトを作成
console.log( emptyMap );
>Map{ }                      //空のMapオブジェクトが定義される

const convertedMap = new Map([   //2次元配列からMapオブジェクトを作成
    [ "キー1", "値1" ],
    [ "キー2", "値2" ]
]);

console.log( convertedMap );
>Map{ "キー1" => "値1", "キー2" => "値2" } 

Mapの場合、オブジェクトのようにドット記法を使って直接、値を取り出すことはできません。Mapオブジェクト内の値の取得・変更・削除は、専用のメソッドを使います。

Mapのメソッド

set 値の設定

setメソッドを使うと、Mapオブジェクトに対して値を追加できます。Mapオブジェクトのキーには、文字列以外も数値、真偽値、オブジェクトなど、すべての型の値を使うことができます。

様々なデータ型をキーにして値を登録

const fruits = new Map();
fruits.set( 1, "apple" );          //数値をキーとして値を登録する
const emptyObj = { };
fruits.set( emptyObj, "orange" );  //オブジェクトをキーとして値を登録する
fruits.set( true, "grape" );       //真偽値をキーとして値を登録
console.log( fruits );
>Map { 1 => "apple", emptyObj => "orange", true => "grape" }

get 値の取得

getメソッドで値を取得します。

キーを指定して値を取得

const fruits = new Map();
const emptyObj = {  };
fruits.set( emptyObj, "orange" );
console.log( fruits.get( emptyObj ) );   //emptyObjをキーに値を取得
>orange

オブジェクトに値を登録した場合には、オブジェクトが格納されているアドレス値をキーに登録したことになります。そのため、オブジェクトの構造が同じでもオブジェクトの格納されているアドレスが異なる場合には、Mapから値を取得できません。

異なるオブジェクトで値を取得しようした場合

const fruits = new Map();
const emptyObj = {  };
fruits.set( emptyObj, "orange" );   //emptyObjのアドレスで値を登録
console.log( fruits.get( { } ) );   //emptyObjと構造が同じ別のオブジェクトで値を取得
>undefined                          //値を取得できない

delete コレクション内の値を削除

deleteメソッドを使うと、Mapオブジェクト内から値を削除できます。

Mapからキーと値のペアを削除

const  fruits = new Map([
    [ 1, "apple"],
    [ false, "orange" ],
]);
fruits.delete( false );      //falseをキーに値を削除
console.log( fruits );
>Map { 1 => "apple" }

clear コレクション内の値をすべて削除

clearメソッドは、Mapオブジェクト内に保持したキーと値のペアをすべて削除します。

すべてのキーと値のペアを削除

const  fruits = new Map([
    [ 1, "apple"],
    [ false, "orange" ],
]);
fruits.clear();         //すべてのキーと値のペアを削除
console.log( fruits );
>Map { }

has コレクション内に値が存在するか確認

hasメソッドは、Mapオブジェクト内にキーが存在するかを確認します。

Mapオブジェクト内にキーが存在するか確認

const  fruits = new Map([
    [ 1, "apple"],
    [ false, "orange" ],
]);
console.log( fruits.has( false ) );   //Mapに含まれるキー
>true
console.log( fruits.has( 2) );        //Mapに含まれないキー
>false

コールバック関数を引数に取るメソッド

コールバック関数を引数に取るMapのメソッドもforEachです

forEach Mapオブジェクトの要素をループ処理

MapのforEachメソッドは、配列のforEachと同様、コールバック関数を使ってループ処理を行います。

構文:forEachの記法

Mapオブジェクト.forEach( value, key, map ){
    /* Mapの各要素を使った処理 */
} [, _this]);
  • value:Mapの値が1つずつ渡されてきます。
  • key :Mapのキーが1つずつ渡されてきます。
  • map :Mapオブジェクト自体が渡されます。
  • _this :コールバック関数内のthisの参照先を設定します。

Mapのループ

const map = new Map( [
    [ "キー1", "値1" ],
    [ "キー2", "値2" ]
]);

map.forEach( (value, key, map) => {
    console.log( `value: [${value}], key:[${key}], map:`, map );
});
実行結果

Mapのプロパティ

size コレクションの長さを取得

Mapオブジェクトでは、Setと同様、sizeプロパティにMapの長さの情報を保持しています。

sizeプロパティからMapの長さを取得

const fruits = new Map();
fruits.set( 1, "apple" );
const emptyObj = {};
fruits.set( emptyObj, "orange" );
console.log( fruits.size );    //Mapの要素数を取得
>2

Mapからオブジェクトへの変換

構文:Mapからオブジェクトへ変換

const オブジェクト = Object.fromEnteries( Mapオブジェクト );

使用例

const map = new Map( [
    [ "キー1", "値1" ],
    [ "キー2", "値2" ]
]);
const obj = Object.fromEntries( map );   //Mapからオブジェクトに変換
console.log( obj[ "キー1" ] );            //オブジェクトのためブラケット記法で値を取得可能
>値1

Mapとオブジェクトの使い分け

Map、オブジェクトのいずれも同じようにキーと値を対にして値を管理するため、一見どちらでも値を管理しても同じに見えるかもしれません。しかし、Mapとオブジェクトには、次の違いがあります。

Mapとオブジェクトの主な違い

Mapの場合、キーに文字列以外も使用可能

Mapでは任意の型の値をキーとして利用可能です。一方、オブジェクトの場合は、文字列とシンボル(Symbol)のみがプロパティとして利用可能です。

Mapの場合、for・・・of文を使った繰り返し処理を記述可能

Mapは、反復可能オブジェクトの一種です。そんため、for・・・of文が使用可能です。一方、オブジェクトの場合は、for…of文を使うことはできません。

Mapの場合、sizeを通して長さを取得可能

Mapのsizeプロパティは、保持する要素数が変わると、自動的に値が変更されます。一方、オブジェクトには、長さを管理するプロパティがありません。

Mapの場合、メソッドを保持できない

Mapは関数を値として保持できますが、関数内でthisを使うようなメソッドとしての機能を実装するには向いてません。そのため、メソッドを必要とする場合に、オブジェクトを使うほうがよい。

このように、単純なコレクション(データの格納領域)として使いたい場合にMap、メソッドなどで保持している値を操作する必要がある場合にはオブジェクトを使います。

WeakMap

WeakMapは、キーにオブジェクトのみ使用可能なコレクションです。Mapでは任意の型の値をキーとして使えますが、WeakMapではオブジェクトのみがキーとして使用可能です。

WeakMapの初期化

const wm = new WeakMap();

WeakMapのメソッド

set 値の設定

setメソッドで、WeakMapオブジェクトに対してキーと値を対にして登録します。

オブジェクトをキーにしてWeakMapに値を設定

const wm = new WeakMap();
let keyObj = { };
wm.set( keyObj, "value" );     //keyObjをキーにして"value"を追加
console.log( wm );
>WeakMap { {…} => "value" }    //keyObjと"value"が対で格納される
wm.set( 1, "value2");          //オブジェクト以外をキーにするとエラーが発生
> Uncaught TypeError: Invalid value used as weak map key //[意訳]型に関数エラー:不正な値がWeakMapのキーとして使用されました。

get 値の取得

getメソッドで、特定のキーに対応する値を取得します。

キーに対応する値の取得

const wm = new WeakMap();
let keyObj = { };
wm.set( keyObj, "value" );
console.log( wm.get( keyObj ) );   //keyObjに対する値の取得
> value

delete コレクション内の値を削除

deleteメソッドで、特定のキーの値を削除します。

キーに一致するキーと値のペアを削除

const wm = new WeakMap();
let keyObj = { };
wm.set( keyObj, "value" );
wm.delete( keyObj );               //キーと値のペアを削除
console.log( wm );
> WeakMap { }

has コレクション内に値が存在するか確認

hasメソッドで、WeakMap内に一致するキーオブジェクトが存在すればtrueを返します。

キーの存在確認

const wm = new WeakMap();
let keyObj = { };
wm.set( keyObj, "value" );
console.log( wm.has( keyObj ) );   //keyObjはWeakMapのキーとして含まれる
> true
console.log( wm.has( { } ) );      //{ }はkeyObjとは別のオブジェクトのため、WeakMapのキーとして含まれない
> false

WeakMapは、反復可能オブジェクトではないため、for…of文などを使った反復処理はできません。また、sizeプロパティも保持していません。単にオブジェクトをキーとした値の保管場所が必要な場合には、WeakMapを使用します。

WeakMapを使うメリット

コレクションに登録する際にキーとして使ったオブジェクトが消滅した場合の挙動の違いです。WeakMapの場合は、「キーとして使ったオブジェクトが使用不可(参照不可)になると、キーと値のペアもWeakMapのコレクションから削除される対象となります(削除対象のペアはガベージコレクションによって適宜削除されます)」。一方、Mapの場合は、キーとして使ったオブジェクトが参照不可になったとしても、キーと値のペアはMapコレクション内に保持し続けられます。

let wm , map;
function fn(){
    const key = {};      //関数fnのスコープで変数keyを宣言しているため、変数keyは関数fn内でのみ参照可能
    wm = new WeakMap;
    wm.set( key, "値" );
    map = new Map;
    map.set( key, "値" );
}                        //関数が終了した時点で変数keyは使用不可(参照不可)になる
fn();
//このスコープでは、変数keyは使用できないため、wm内のキーと値のペアもガベージコレクションの対象となる
for( const pair of map ){
    console.log( pair[0], pair[1] );
}
>{} "値"                 //map内には、不要な値としてキーと値のペアが保持し続けられる。

そのため、Mapで使用不可なオブジェクトが増えてくると、不要なメモリ領域をどんどん占有していくことになります。このように不要なメモリが破棄されずに残ってしまう状態をメモリリリークと呼びます。一方、WeakMapのように、キーが参照不可になった時点でコレクション内のキーと値のペアを削除対象(ガベージコレクションの対象)とするような性質を弱参照と呼びます。