JavaScript/TypeScript

Map と Set を使う

5
  • JavaScript

Map と Set を使う — JavaScript Tips

オブジェクトのキーは文字列か Symbol に寄ります。任意の型をキーにしたいときが Map、重複しない集合が Set です。参照ベースの一意性や、そのままでは JSON 化できない点にも注意が要ります。

参考(MDN)


目次

  1. なぜ Map と Set が必要か
  2. Map の基本操作
  3. Map とオブジェクトの使い分け
  4. Set の基本操作
  5. 重複排除の実用パターン
  6. Map と Set の反復
  7. WeakMap / WeakSet の入口
  8. うまくいかないとき

なぜ Map と Set が必要か

オブジェクトのキーは文字列(または Symbol)

通常のオブジェクト {} では、キーは内部的に文字列か Symbol に変換されます。

const obj = {};const key = { id: 1 };obj[key] = "値";console.log(obj["[object Object]"]); // "値" — オブジェクトが文字列キーに

同じオブジェクト参照をキーとして区別したい場合、オブジェクトでは意図どおり動きません。

配列での重複チェックは O(n)

配列で「すでに含まれているか」を毎回 includes すると、要素数に比例して遅くなります。Set なら has が一般的に高速です。

const arr = [1, 2, 3, 2, 1];console.log(arr.includes(2)); // true — 配列全体を走査const set = new Set(arr);console.log(set.has(2)); // true — ハッシュ的な検索

Map の基本操作

Mapキーと値のペア を保持します。キーにオブジェクト・関数・数値など、任意の型を使えます。

const user = { id: 1, name: "田中" };const cache = new Map();cache.set(user, { lastLogin: "2026-07-12" });console.log(cache.get(user)); // { lastLogin: "2026-07-12" }cache.set("theme", "dark");cache.set(42, "answer");console.log(cache.size);      // 3console.log(cache.has("theme")); // truecache.delete(42);console.log(cache.size);      // 2

よく使う API

メソッド 説明
set(key, value) 追加・更新
get(key) 取得(なければ undefined
has(key) キーの有無
delete(key) 削除
clear() 全削除
size 件数(プロパティではなく getter)

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

観点 Map オブジェクト {}
キーの型 任意 文字列・Symbol が基本
順序 挿入順を保持 整数風キーは並びが特殊(ES2015+ でも注意)
サイズ map.size Object.keys(obj).length
JSON そのまま不可 そのままシリアライズ可
プロトタイプ 余計なキーが入らない toString などを継承
用途 動的な辞書・キャッシュ 固定スキーマのレコード

オブジェクトが向く例 — スキーマが決まったデータ

const product = {  id: "p-100",  name: "ノート",  price: 350,};

Map が向く例 — キーが動的・オブジェクト参照・頻繁な追加削除

const sessions = new Map();function getSession(token) {  if (!sessions.has(token)) {    sessions.set(token, { createdAt: Date.now(), data: {} });  }  return sessions.get(token);}

Object.create(null) でプロトタイプなしオブジェクトを作る手もあります。サイズ取得や反復のしやすさでは Map の方が素直な場面が多いです。


Set の基本操作

Set重複しない値の集合 です。

const tags = new Set();tags.add("js");tags.add("web");tags.add("js"); // 重複は無視console.log(tags.size);  // 2console.log(tags.has("js")); // truetags.delete("web");console.log([...tags]); // ["js"] — 配列へ展開

コンストラクタに反復可能オブジェクトを渡すと、初期値をまとめて登録できます。

const unique = new Set([1, 2, 2, 3, 3, 3]);console.log([...unique]); // [1, 2, 3]

重複排除の実用パターン

配列のユニーク化

const ids = [10, 20, 10, 30, 20, 40];const uniqueIds = [...new Set(ids)];console.log(uniqueIds); // [10, 20, 30, 40]

オブジェクト配列をキーで一意にする

Set はオブジェクトの「中身」ではなく 参照 で一意性を見ます。プロパティで重複を除くには Map を使います。

const users = [  { id: 1, name: "A" },  { id: 2, name: "B" },  { id: 1, name: "A 改" },];const byId = new Map();for (const user of users) {  byId.set(user.id, user); // 同じ id は上書き}const uniqueUsers = [...byId.values()];console.log(uniqueUsers.length); // 2

許可リスト・禁止リスト

const ALLOWED = new Set(["GET", "HEAD", "OPTIONS"]);function isAllowed(method) {  return ALLOWED.has(method.toUpperCase());}console.log(isAllowed("get"));  // trueconsole.log(isAllowed("POST")); // false

二重送信の防止(簡易)

const submitted = new Set();function submitOnce(orderId) {  if (submitted.has(orderId)) {    return { ok: false, reason: "already submitted" };  }  submitted.add(orderId);  // 実際の送信処理...  return { ok: true };}

Map と Set の反復

どちらも 反復可能(iterable) なので for...of が使えます。

const scores = new Map([  ["数学", 90],  ["英語", 75],  ["理科", 88],]);for (const [subject, score] of scores) {  console.log(`${subject}: ${score}`);}for (const subject of scores.keys()) {  console.log(subject);}for (const score of scores.values()) {  console.log(score);}

Set も同様です。

const colors = new Set(["赤", "緑", "青"]);for (const color of colors) {  console.log(color);}

配列メソッド風に書きたいときはスプレッドで配列に変換します。

const doubled = [...scores.values()].map((s) => s * 2);

WeakMap / WeakSet の入口

WeakMapWeakSetオブジェクトのみ をキー(または値)に取り、参照が他に残っていなければガベージコレクションの対象にできます。DOM ノードや一時的なメタデータの紐づけなど、メモリリークを避けたい場面で使われます。

const wm = new WeakMap();let element = { tag: "div" }; // 実際は DOM 要素wm.set(element, { clicks: 0 });console.log(wm.get(element)); // { clicks: 0 }element = null; // 参照を外すと、WeakMap のエントリも回収されうる

Weak 系は 反復不可size プロパティなし という制約があります。キャッシュ全件の列挙が必要なら通常の Map を使います。


うまくいかないとき

オブジェクトを Set に入れて「同じ内容」が重複する

const set = new Set();set.add({ id: 1 });set.add({ id: 1 });console.log(set.size); // 2 — 別オブジェクトとして扱われる

中身で一意にしたいなら、上記のように Map で id をキーにします。

Map のキーに NaN

Map / Set の比較は SameValueZero なので、NaN !== NaN でも NaN 同士は同じキー(値)として扱われます

const map = new Map();map.set(NaN, "not a number");console.log(map.get(NaN)); // "not a number"

通常の === とは違う点です。キーは文字列や数値に揃えることが多いです。

JSON.stringify しようとして空になる

const map = new Map([["a", 1]]);JSON.stringify(map); // "{}" — Map は直接 JSON 化できないconst plain = Object.fromEntries(map);JSON.stringify(plain); // '{"a":1}'

オブジェクトで数値キーが並び変わる

const o = {};o[10] = "ten";o[2] = "two";console.log(Object.keys(o)); // ["2", "10"] — 整数風キーはソートされる

挿入順をそのまま保ちたい辞書には Map が適しています。

Set でオブジェクトの配列をユニーク化できない

const list = [{ v: 1 }, { v: 1 }];const s = new Set(list);console.log(s.size); // 2

プロパティ値で比較するユニーク化は、Mapfilter + find、または専用の正規化関数が必要です。

シェア