JavaScript | ES6+ 文法:新データ構造 – Map vs Object

JavaScript JavaScript
スポンサーリンク

まずざっくりイメージ:Map と Object は「役割」が違う

同じように「キーと値」を扱えるので、最初は

「Map でも Object でもできるじゃん。どっち使えばいいの?」

と感じると思います。

ざっくり分けると、こうです。

  • Object:
    「1人のユーザー」や「1つの商品」みたいな、
    “1件のデータ(レコード)” を表すための入れ物
  • Map:
    「ユーザーID → ユーザー情報」みたいな、
    “キーと値の集まり(辞書・マップ)” を扱うためのコレクション

つまり、

  • 「1つのものの属性(id, name, age …)を持ちたい」 → Object
  • 「たくさんのキーと値を、出し入れ・検索したい」 → Map

という意識を持つと、かなり整理されます。

基本的な使い方の違いをコードで見る

Object でキーと値

const user = {
  id: 1,
  name: "Alice",
  age: 20,
};

// 取得
console.log(user.name);     // Alice
console.log(user["age"]);   // 20

// 追加
user.email = "alice@example.com";

// 削除
delete user.age;
JavaScript

Object は「その1人のユーザーの情報」を表すのに向いています。
プロパティ名は基本、文字列(かシンボル)です。

Map でキーと値

const userMap = new Map();

// 追加
userMap.set("id", 1);
userMap.set("name", "Alice");

// 取得
console.log(userMap.get("name")); // Alice

// 存在チェック
console.log(userMap.has("id"));   // true

// 削除
userMap.delete("id");

// 要素数
console.log(userMap.size);        // 1
JavaScript

ぱっと見似ていますが、

  • 「set / get / has / delete / size」という専用メソッド(プロパティ)で扱える
  • キーにオブジェクトなども使える
  • 挿入順でループできる

という点が大きく違います。

重要な違い1:キーに何を使えるか

Object のキーは基本「文字列」

const obj = {};
obj["123"] = "number key";
obj[123]   = "also number key"; // 実際は "123" に変換される

console.log(obj["123"]); // also number key
console.log(obj[123]);   // also number key(同じ)
JavaScript

数値で書いても、内部的には文字列になります。
また、オブジェクトをキーにしても、結局 "[object Object]" などに文字列化されてしまいます。

const objKey = { id: 1 };
const obj = {};

obj[objKey] = "value";
console.log(obj); // { "[object Object]": "value" }
JavaScript

Map は「どんな値でも」キーにできる

const m = new Map();

const objKey = { id: 1 };
const arrKey = [1, 2, 3];

m.set(objKey, "オブジェクトに紐づく値");
m.set(arrKey, "配列に紐づく値");
m.set(123, "数値キー");
m.set(true, "真偽値キー");

console.log(m.get(objKey)); // オブジェクトに紐づく値
console.log(m.get(arrKey)); // 配列に紐づく値
JavaScript

ここが非常に重要です。

  • Object:
    キーは「文字列かシンボル」。
    オブジェクトをキーにする発想とは相性が悪い。
  • Map:
    キーは 何でも OK(オブジェクト・配列・関数もそのままキーになる)。

「このオブジェクトに対する付随情報を持ちたい」
「DOM 要素ごとに状態を持ちたい」
というような場面では、Map の方が圧倒的に自然です。

重要な違い2:サイズの取得・追加/削除のしやすさ

Object のサイズを数えるのはひと手間

Object に「いくつプロパティがあるか」を知るには、こんな書き方になります。

const obj = { a: 1, b: 2, c: 3 };

const size = Object.keys(obj).length;
console.log(size); // 3
JavaScript

毎回 Object.keys(obj).length と書くのは、少しもっさりしています。

Map は size プロパティで一発

const m = new Map();
m.set("a", 1);
m.set("b", 2);
m.set("c", 3);

console.log(m.size); // 3
JavaScript

この違いは、「辞書」としてガンガン要素を出し入れする場面で効いてきます。

また、存在チェックや削除も Map の方が素直です。

Object:

const obj = { a: 1 };

console.log("a" in obj); // true

delete obj.a;
JavaScript

Map:

const m = new Map();
m.set("a", 1);

console.log(m.has("a")); // true

m.delete("a");
JavaScript

ここが重要です。
「増やす・減らす・存在チェック・サイズ確認」を頻繁にやるなら、Map の方が読みやすく、間違いにくい

重要な違い3:ループと順序

Object のループ

Object をループするときは for...inObject.keys/values/entries を使います。

const obj = { a: 1, b: 2, c: 3 };

for (const key in obj) {
  console.log(key, obj[key]);
}
// a 1
// b 2
// c 3
JavaScript

最近の仕様では「追加した順」がある程度守られますが、
歴史的には少しややこしい仕様があり、完全に「順序付きの辞書」として設計されているわけではありません。

Map のループは「挿入順」で安定している

const m = new Map();
m.set("a", 1);
m.set("b", 2);
m.set("c", 3);

for (const [key, value] of m) {
  console.log(key, value);
}
// a 1
// b 2
// c 3
JavaScript

for...of で回すと [key, value] のペアが順番に出てきます。
keys(), values(), entries() も使えます。

for (const key of m.keys()) {
  console.log("key:", key);
}

for (const value of m.values()) {
  console.log("value:", value);
}
JavaScript

Map は「順序付きのキー集合」として明確に設計されています。
順番に意味がある「設定の一覧」「ミドルウェアの登録順」などには Map の方が安心して使えます。

重要な違い4:プロトタイプ汚染・特別なキーを気にしなくてよい

Object はプロトタイプを持つ

Object は Object.prototype を継承しているので、
toStringhasOwnProperty など、最初からいくつかのプロパティを持っています。

const obj = {};
console.log("toString" in obj); // true
JavaScript

つまり、ユーザーが "toString" というキーを使うとき、
微妙に気をつける必要があったりします(最近は Object.create(null) を使うなどのテクニックもありますが、初心者には少し遠い話)。

Map には「余計なプロパティ」がない

Map は純粋に「キー → 値」を扱うための専用コレクションです。
toString のような紛らわしいプロパティは、最初から存在しません。

const m = new Map();
console.log(m.has("toString")); // false
JavaScript

ここが重要です。
Object は「JS の基礎構造」としての歴史的な事情が多い。
Map は「キーと値の辞書」のために設計された、より素直なデータ構造。

「辞書としてだけ見たい」なら、多くの場合 Map の方が考えることが少なくて済みます。

何をするときに Object、何をするときに Map?

Object が向いている場面

Object は、「1件のデータ」「固定された項目」を扱うときに向いています。

例えば、ユーザー情報:

const user = {
  id: 1,
  name: "Alice",
  age: 20,
};
JavaScript

JSON でやり取りするデータは基本的に Object ですし、
クラスのインスタンスのプロパティも Object の世界です。

「このユーザーが何者か」を表現する単位として、Object はとても自然です。

Map が向いている場面

Map は、「たくさんのキーと値のペア」を扱うときに向いています。

例えば:

ユーザーID → ユーザーオブジェクト
キャッシュキー → 計算結果
DOM 要素 → 追加情報

など、「キー集合」としての性格が強いものです。

例:ユーザーID → ユーザーのマップ

const users = new Map();

users.set(1, { id: 1, name: "Alice" });
users.set(2, { id: 2, name: "Bob" });

console.log(users.get(1)); // { id: 1, name: "Alice" }
JavaScript

例:DOM 要素 → イベント用の補助情報

const elementState = new Map();

function setState(el, state) {
  elementState.set(el, state);
}

function getState(el) {
  return elementState.get(el);
}
JavaScript

よくある「間違った使い方」

よくあるパターンとして、

  • 「全部の設定を 1 つの Object に突っ込んで、あとからどんどん増やす」
  • 「キーの数を数える」「削除を頻繁にやる」のに Object を辞書として使い続ける

というのがあります。

そういうときは、

「これ、本当は “1件のデータ” じゃなくて、“キーと値の集合” を扱いたいんじゃない?」

と自分に問いかけてみてください。
答えが Yes なら、Map を使うとコードが一気にスッキリする可能性が高いです。

具体例で「Map と Object の役割分担」を体感する

例1:Object で「1件のユーザー」、Map で「ユーザー一覧」

// 1人のユーザー(Object が自然)
const user1 = {
  id: 1,
  name: "Alice",
};

const user2 = {
  id: 2,
  name: "Bob",
};

// ユーザーID → ユーザーのマップ(Map が自然)
const userMap = new Map();
userMap.set(user1.id, user1);
userMap.set(user2.id, user2);

console.log(userMap.get(1)); // user1
console.log(userMap.get(2)); // user2
JavaScript

「個々のユーザー」には Object、
「それらの一覧・辞書」として Map、という分担がキレイです。

例2:頻度カウンター(Map のほうが素直)

文字列中の文字の出現回数を数える例です。

Object で書くと:

function countCharsObj(str) {
  const counter = {};

  for (const ch of str) {
    if (counter[ch] === undefined) {
      counter[ch] = 0;
    }
    counter[ch]++;
  }

  return counter;
}

console.log(countCharsObj("hello"));
// { h: 1, e: 1, l: 2, o: 1 }
JavaScript

Map で書くと:

function countCharsMap(str) {
  const counter = new Map();

  for (const ch of str) {
    const count = counter.get(ch) || 0;
    counter.set(ch, count + 1);
  }

  return counter;
}

const result = countCharsMap("hello");
for (const [ch, count] of result) {
  console.log(ch, count);
}
// h 1
// e 1
// l 2
// o 1
JavaScript

どちらも書けますが、Map の get / set の方が「辞書を扱っている」という意図が伝わりやすいです。

例3:キーにオブジェクトや DOM を使う(Map の独壇場)

const button1 = document.createElement("button");
const button2 = document.createElement("button");

const clickCount = new Map();

function registerClickCount(button) {
  clickCount.set(button, 0);

  button.addEventListener("click", () => {
    const current = clickCount.get(button) ?? 0;
    clickCount.set(button, current + 1);
    console.log("クリック回数:", clickCount.get(button));
  });
}

registerClickCount(button1);
registerClickCount(button2);
JavaScript

ここで Object を使うのは現実的ではありません。
button 自体をキーにできる、という Map の性質が効いています。

まとめ:Map vs Object の「考え方の軸」

最後に、実際に選ぶときの軸を言い切ります。

  • 「1件のデータ(レコード)を表したいか?」
    → Yes なら Object
  • 「キーと値の集まり(辞書)を扱いたいか?」
    → Yes なら Map
  • 「キーにオブジェクトや DOM 要素を使いたいか?」
    → Yes なら絶対に Map
  • 「頻繁に追加・削除・存在チェック・サイズ確認をするか?」
    → Yes なら Map のほうが素直で安全
  • 「JSON にしたい」「API レスポンスをそのまま扱いたい」
    → 基本 Object の世界

ここが重要です。
どちらでも「技術的には」できる場合でも、「意味としてどちらがしっくりくるか」を優先する

「これは 1人のユーザーのデータだから Object」
「これはユーザーの一覧で、ID から検索したいから Map」

と、自分の中で筋が通っていると、コードの読みやすさが大きく変わります。

まずは、自分のコードの中で「辞書っぽい Object」を一つ見つけて、
それを Map に書き換えてみてください。

どこが楽になるか、どんなときに Map が気持ちいいかが、
体感でつかめてきます。

タイトルとURLをコピーしました