Map と Set を使う — JavaScript Tips
オブジェクトのキーは文字列か Symbol に寄ります。任意の型をキーにしたいときが Map、重複しない集合が Set です。参照ベースの一意性や、そのままでは JSON 化できない点にも注意が要ります。
参考(MDN)
目次
- なぜ Map と Set が必要か
- Map の基本操作
- Map とオブジェクトの使い分け
- Set の基本操作
- 重複排除の実用パターン
- Map と Set の反復
- WeakMap / WeakSet の入口
- うまくいかないとき
なぜ 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 の入口
WeakMap と WeakSet は オブジェクトのみ をキー(または値)に取り、参照が他に残っていなければガベージコレクションの対象にできます。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プロパティ値で比較するユニーク化は、Map・filter + find、または専用の正規化関数が必要です。