8.

JavaScript の連想配列|オブジェクトと Map の使い分け

編集
この記事の要点
  • JavaScript の連想配列はオブジェクト {}。専用の型ではない
  • オブジェクトのキーは文字列(か Symbol)だけ。数値を入れても文字列になる
  • キーが動的に増減する・件数が多いなら Map を使う
  • 件数は Object.keys(obj).lengthobj.lengthundefined
  • キーの有無は Object.hasOwn(obj, key)obj[key]undefined 判定では区別できない

オブジェクトで作る

const user = {
  name: "田中",
  age: 30,
  "favorite color": "青",     // 記号や空白を含むキーはクォートで囲む
};

console.log(user.name);           // 田中          ドット記法
console.log(user["age"]);         // 30            ブラケット記法
console.log(user["favorite color"]);

const key = "name";
console.log(user[key]);           // 田中          変数で指定するときはブラケット

user.city = "東京";                // 追加
user.age = 31;                    // 更新
delete user.age;                  // 削除

// 変数名とキー名が同じなら省略できる
const name = "鈴木", age = 25;
const user2 = { name, age };      // { name: "鈴木", age: 25 }

// キーを変数から作る
const field = "score";
const data = { [field]: 100 };    // { score: 100 }

キーは文字列になる

const obj = {};
obj[1] = "a";
obj["1"] = "b";           // 同じキーとして上書きされる
console.log(obj);         // { "1": "b" }

obj[true] = "c";          // "true"
obj[null] = "d";          // "null"
obj[{ id: 1 }] = "e";     // "[object Object]"  ← オブジェクトは区別できない

console.log(Object.keys(obj));   // ["1", "true", "null", "[object Object]"]

オブジェクトをキーにすると全部同じ "[object Object]" になります。この用途には Map が必要です。

Map を使う

const m = new Map();

m.set("name", "田中");
m.set(1, "数値のキー");
m.set(true, "真偽のキー");
const objKey = { id: 1 };
m.set(objKey, "オブジェクトのキー");   // 参照そのものがキーになる

console.log(m.get("name"));      // 田中
console.log(m.get(1));           // 数値のキー("1" とは別)
console.log(m.get(objKey));      // オブジェクトのキー
console.log(m.has("name"));      // true
console.log(m.size);             // 4          件数は size
m.delete("name");
m.clear();

// まとめて作る
const m2 = new Map([["a", 1], ["b", 2]]);

// 回す(挿入順が保証される)
for (const [k, v] of m2) console.log(k, v);
m2.forEach((v, k) => console.log(k, v));   // 引数の順が (値, キー) なので注意
オブジェクト {}Map
キーの型文字列 / Symbol のみ何でも
件数Object.keys(o).lengthm.size
順序整数キーが先に来る挿入順
頻繁な追加削除遅くなりやすい速い
JSON にできるできるできない(変換が要る)
プロトタイプの汚染toString などが最初から見える無い

設定値や API のレスポンスのように「形が決まっているもの」はオブジェクト、キーが実行時に決まるものは Map と使い分けます。

キーの順序

const o = { b: 1, 2: 2, a: 3, 1: 4 };
console.log(Object.keys(o));     // ["1", "2", "b", "a"]
// 整数に見えるキーが昇順で先に来て、そのあとが挿入順

const m = new Map([["b", 1], [2, 2], ["a", 3], [1, 4]]);
console.log([...m.keys()]);      // ["b", 2, "a", 1]   挿入順のまま

ID をキーにした一覧を「登録順」で扱いたい場合、オブジェクトだと勝手に数値順に並び替わります。順序が意味を持つなら Map か配列を使ってください。

走査と変換

const scores = { math: 80, english: 70, science: 90 };

Object.keys(scores);      // ["math", "english", "science"]
Object.values(scores);    // [80, 70, 90]
Object.entries(scores);   // [["math",80], ["english",70], ["science",90]]

for (const [k, v] of Object.entries(scores)) {
  console.log(`${k}: ${v}`);
}

// 合計・最大
const total = Object.values(scores).reduce((a, b) => a + b, 0);   // 240
const best = Object.entries(scores).sort((a, b) => b[1] - a[1])[0];

// 値を変換した新しいオブジェクトを作る
const doubled = Object.fromEntries(
  Object.entries(scores).map(([k, v]) => [k, v * 2]),
);

// 条件で絞る
const high = Object.fromEntries(
  Object.entries(scores).filter(([, v]) => v >= 80),
);

// Map ⇔ オブジェクト
const m = new Map(Object.entries(scores));
const back = Object.fromEntries(m);

キーの存在確認

const o = { a: 1, b: undefined };

console.log(o.b);                      // undefined
console.log(o.c);                      // undefined   ← 区別できない

console.log("b" in o);                 // true
console.log("c" in o);                 // false
console.log("toString" in o);          // true  ← 継承したものも拾う

console.log(Object.hasOwn(o, "b"));    // true   自分のキーだけ見る(推奨)
console.log(Object.hasOwn(o, "toString")); // false

// 安全に読む
const v = o.c ?? "既定値";              // null / undefined のときだけ既定値
const deep = data?.user?.name;         // 途中が無くてもエラーにならない

コピーとマージ

const defaults = { debug: false, level: 1 };
const options = { level: 3 };

const merged = { ...defaults, ...options };      // { debug: false, level: 3 }
const merged2 = Object.assign({}, defaults, options);

// 浅いコピーなので入れ子は共有される
const a = { inner: { n: 1 } };
const b = { ...a };
b.inner.n = 2;
console.log(a.inner.n);        // 2

const c = structuredClone(a);  // 深いコピー
c.inner.n = 3;
console.log(a.inner.n);        // 2

JSON との相互変換

const o = { name: "田中", tags: ["a"], at: new Date(), fn: () => {} };

console.log(JSON.stringify(o));
// {"name":"田中","tags":["a"],"at":"2026-09-07T03:00:00.000Z"}
// 関数と undefined は消える。Date は文字列になる

const back = JSON.parse('{"a":1}');

// Map は JSON にできない
JSON.stringify(new Map([["a", 1]]));            // "{}"
JSON.stringify([...new Map([["a", 1]])]);       // [["a",1]]

集計に使う

const words = ["a", "b", "a", "c", "a"];

// 出現回数を数える
const count = {};
for (const w of words) count[w] = (count[w] ?? 0) + 1;
console.log(count);              // { a: 3, b: 1, c: 1 }

// Map なら初期化が明快
const m = new Map();
for (const w of words) m.set(w, (m.get(w) ?? 0) + 1);

// キーごとにまとめる
const users = [
  { team: "A", name: "田中" },
  { team: "B", name: "鈴木" },
  { team: "A", name: "佐藤" },
];
const byTeam = {};
for (const u of users) (byTeam[u.team] ??= []).push(u.name);
console.log(byTeam);             // { A: ["田中","佐藤"], B: ["鈴木"] }

// ID を鍵にした引き当て表を作る
const index = Object.fromEntries(users.map((u) => [u.name, u]));
const index2 = new Map(users.map((u) => [u.name, u]));

(obj[key] ??= []).push(v) は「無ければ空配列を作ってから追加」を 1 行で書く定型です。件数が多いなら Map のほうが速くなります。

プロトタイプに注意する

const o = {};

console.log(o.toString);            // 関数(何も入れていないのに存在する)
console.log(o["constructor"]);      // 関数
console.log("toString" in o);       // true

// 利用者の入力をキーにすると誤判定する
const key = "toString";
if (o[key]) console.log("登録済み");   // 登録していないのに通ってしまう

// 対処 1: hasOwn で確認する
if (Object.hasOwn(o, key)) { }

// 対処 2: プロトタイプを持たないオブジェクトを作る
const dict = Object.create(null);
console.log(dict.toString);         // undefined

// 対処 3: Map を使う(最も簡単)
const m = new Map();
console.log(m.has("toString"));     // false

利用者の入力をキーにする辞書には MapObject.create(null) を使ってください。普通のオブジェクトだと __proto__ のようなキーで挙動が壊れます(プロトタイプ汚染)。

WeakMap

const cache = new WeakMap();

function getData(el) {
  if (!cache.has(el)) {
    cache.set(el, { rendered: false });
  }
  return cache.get(el);
}

const el = document.querySelector("#a");
getData(el).rendered = true;

// el が DOM から消えて参照が無くなると、cache の中身も自動で解放される
MapWeakMap
キーにできるもの何でもオブジェクトのみ
回せるかできるできないsize も無い)
キーが不要になったら残り続ける自動で解放される

DOM 要素に付随する情報を持たせる用途では、Map だと要素を削除してもエントリが残りメモリリークになります。この場合は WeakMap を使ってください。

Object と Map の変換早見表

const o = { a: 1, b: 2 };
const m = new Map([["a", 1], ["b", 2]]);

new Map(Object.entries(o));        // Object → Map
Object.fromEntries(m);             // Map → Object
[...m];                            // Map → 二次元配列
[...m.keys()];                     // ["a","b"]
[...m.values()];                   // [1,2]

// URL のクエリも同じ形で扱える
const params = new URLSearchParams("a=1&b=2");
Object.fromEntries(params);        // { a: "1", b: "2" }(値は文字列)

// FormData も同様
const fd = new FormData(document.querySelector("form"));
Object.fromEntries(fd);            // 同名フィールドは最後の 1 つだけ残る

URLSearchParamsFormData同じキーを複数持てますObject.fromEntries() で変換すると最後の 1 件しか残らないため、チェックボックスの複数選択では getAll() を使ってください。

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. 記述方法
  2. コメント
  3. 変数の宣言
  4. 関数
  5. 演算子
  6. 条件文
  7. 配列
  8. 連想配列
  9. ループ処理
  10. 非同期処理
  11. 同期処理
  12. 確認ウィンドウを表示する方法
  13. 文字の置換
  14. base urlを取得する方法
  15. formのsubmit前にjavascriptを呼び出す方法
  16. undefinedのイコール判定
  17. Javascript のみで form を post で submit する方法

最近更新/作成されたページ