JavaScript | ES6+ 文法:スプレッド構文 – オブジェクト展開

JavaScript
スポンサーリンク

オブジェクトのスプレッド構文とは何か

オブジェクトのスプレッド構文は、{ ...obj } のように書いて「オブジェクトの中身(プロパティ)をその場に展開する」書き方です。イメージとしては、「そのオブジェクトの key: value を、ここにズラッと書き出す」という動きをしてくれます。ここが重要です:{ ...obj } で“浅いコピー(shallow copy)”、{ ...a, ...b } で“マージ(結合)”、{ ...base, override: 123 } で“上書きつきのコピー”が簡単に書けます。

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

// 中身をコピーして新しいオブジェクトを作る
const copy = { ...user };
console.log(copy);      // { id: 1, name: "Alice" }
console.log(copy === user); // false(別オブジェクト)
JavaScript

オブジェクトコピーとしてのスプレッド(元データを壊さない)

代入との違い(参照コピー vs 内容コピー)

const copy = original; と書くと、「同じオブジェクトを指す別名」を作っているだけなので、どちらかを変更するともう片方にも影響します。スプレッド構文の { ...original } は、「同じ中身を持つ“別のオブジェクト”」を作ります(浅いコピー)。ここが重要です:元のオブジェクトを壊さずに変更したいときは、必ずスプレッドで“新しいオブジェクト”を作ってから変更します。

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

const alias = user;         // 参照コピー(同じものを指す)
const copied = { ...user }; // 内容コピー(別オブジェクト)

alias.name = "Bob";

console.log(user.name);   // "Bob"(alias と user は同じオブジェクト)
console.log(copied.name); // "Alice"(こちらは変わらない)
JavaScript

“浅いコピー”であること(ネストは共有される)

スプレッドによるコピーは「一段目のプロパティだけ」をコピーします。中にオブジェクトが入っている場合、その中身は“参照を共有”します。

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

const copy = { ...data };
copy.profile.name = "Bob";

console.log(data.profile.name);  // "Bob"(profile は同じオブジェクト)
console.log(copy.profile.name);  // "Bob"
JavaScript

深いところまで完全に別物にしたい場合は、“ディープコピー”用の処理が別途必要になります。まずは「スプレッドは浅いコピー」と覚えておくと安全です。

オブジェクトマージ(結合・上書き)の基本パターン

複数オブジェクトをマージする

{ ...a, ...b } のように書くと、「a の中身を書き出したあとに b の中身を書き出す」動きになります。キーが被った場合は、あとから書かれた方が上書きします。ここが重要です:順番がとても大事です。右側ほど“強い”。

const base = { id: 1, name: "Alice" };
const extra = { admin: true, name: "Alice A." };

const merged = { ...base, ...extra };
console.log(merged);
// { id: 1, name: "Alice A.", admin: true }
// name は extra 側で上書きされた
JavaScript

「元をそのまま」+「一部だけ変更」の定番パターン

設定や状態などを扱うとき、“元のオブジェクトを壊さずに、一部だけ変えて新しいものを作る”ことがよくあります。ここが重要です:{ ...old, 変更したいプロパティ: 新しい値 } というパターンを覚えると、バグがぐっと減ります。

const config = { theme: "light", lang: "ja", debug: false };

// debug だけ true にした新しい設定を作る
const newConfig = { ...config, debug: true };

console.log(config);    // { theme: "light", lang: "ja", debug: false }
console.log(newConfig); // { theme: "light", lang: "ja", debug: true }
JavaScript

これは React の state 更新などでも頻出する“お約束”の書き方です。

分割代入の rest とセットで使う(取り出し+残りをそのまま)

プロパティを取り出しつつ、残りをまとめる

オブジェクトの分割代入の ...rest とスプレッドはセットで覚えると強いです。

分割代入の ...rest(左側)は「残りのプロパティをまとめて受け取る」
スプレッドの ...obj(右側)は「プロパティをバラして並べる」

この組み合わせで、「必要なものだけ抜き出し、残りをそのまま渡す」というパターンが自然に書けます。

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

// id だけ取り出して、残りを others にまとめる
const { id, ...others } = user;

console.log(id);      // 1
console.log(others);  // { name: "Alice", age: 20, admin: true }

// その others に、さらにフラグを足した新しいオブジェクトを作る
const extended = { ...others, active: true };
console.log(extended);
// { name: "Alice", age: 20, admin: true, active: true }
JavaScript

「主役だけ明示的に扱い、残りは“ひとまとめ”で次へ渡す」設計にすると、コードが拡張に強くなります。

関数引数でのオブジェクト展開(オプション上書き)

デフォルト設定+呼び出し側のオプションをマージ

関数に「設定オブジェクト」を渡すとき、デフォルト値とマージして使うのが定番です。ここが重要です:{ ...defaultOptions, ...options } と書けば、「デフォルトをベースに、呼び出し側の指定で上書きする」形になります。

const defaultOptions = {
  method: "GET",
  cache: "no-cache",
  timeout: 3000
};

function request(url, options = {}) {
  const opts = { ...defaultOptions, ...options };
  console.log("url:", url);
  console.log("opts:", opts);
  // 実際のリクエスト処理はこの後に書く
}

request("/api/users", { method: "POST", timeout: 5000 });
/*
opts:
{
  method: "POST",     // 上書き
  cache: "no-cache",  // デフォルト
  timeout: 5000       // 上書き
}
*/
JavaScript

部分的にオプションを変えた新オブジェクトを返す

既存の設定をベースに「少しだけ違う設定」を作るときも同じです。

function withTimeout(config, newTimeout) {
  return { ...config, timeout: newTimeout };
}

const base = { url: "/api", timeout: 3000 };
const fast = withTimeout(base, 1000);
console.log(base); // そのまま
console.log(fast); // timeout だけ 1000 に
JavaScript

よくある落とし穴と注意点(重要ポイントの深掘り)

浅いコピーであること(中のオブジェクトは共有)

もう一度強調します。{ ...obj } は浅いコピーです。入れ子のオブジェクトや配列は共有されます。ネストした部分を“完全に独立したコピー”にしたい場合は、別の処理(ディープコピー)が必要です。

const state = {
  user: { name: "Alice" },
  items: [1, 2, 3]
};

const copy = { ...state };
copy.user.name = "Bob";
copy.items.push(4);

console.log(state.user.name); // "Bob"(共有)
console.log(state.items);     // [1, 2, 3, 4](共有)
JavaScript

「外側の構造だけ変えたい(プロパティの追加・削除・上書き)」ケースではスプレッドで十分ですが、「中身も完全に独立させたい」なら、要素ごとのコピーなどを検討しましょう。

順番で結果が変わる(右側ほど強く上書き)

{ ...a, ...b }{ ...b, ...a } は全く別物です。同じキーがあると「後ろに書いた方」が勝ちます。ここが重要です:
「デフォルト → それを上書きする実際の値」という順に並べるのが基本のパターンです。

const a = { x: 1, y: 2 };
const b = { y: 99, z: 3 };

const ab = { ...a, ...b }; // { x: 1, y: 99, z: 3 }
const ba = { ...b, ...a }; // { y: 2, z: 3, x: 1 }
JavaScript

どちらを優先したいかを、順番でハッキリ表現するようにしましょう。

プロパティの「削除」はできない(除外は分割代入で)

スプレッドは「展開する」「上書きする」だけで、「削除」はできません。特定のプロパティを除外したいときは、

  1. 分割代入で「使わないキーだけ名前を取り出して捨てる」
  2. 残りを ...rest にまとめる
  3. それをスプレッドで展開する

という手順を取ります。

const user = { id: 1, password: "secret", name: "Alice" };

// password を除外したオブジェクトを作りたい
const { password, ...safeUser } = user;
console.log(safeUser); // { id: 1, name: "Alice" }
JavaScript

このパターンは「公開してはいけない情報を落とす」などでよく使います。

例題で理解を固める

// 1) プロフィール情報のマージ
const baseProfile = { name: "Alice", age: 20 };
const additional = { age: 21, country: "Japan" };
const merged = { ...baseProfile, ...additional };
console.log(merged);
// { name: "Alice", age: 21, country: "Japan" }

// 2) 表示用オブジェクトにフラグ追加(元を変えない)
const user = { id: 1, name: "Alice" };
const viewUser = { ...user, isOnline: true };
console.log(user);     // { id: 1, name: "Alice" }
console.log(viewUser); // { id: 1, name: "Alice", isOnline: true }

// 3) 設定の安全な上書き
const defaultCfg = { theme: "light", fontSize: 14, lang: "ja" };
const userCfg = { fontSize: 16 };
const cfg = { ...defaultCfg, ...userCfg };
console.log(cfg);
// { theme: "light", fontSize: 16, lang: "ja" }

// 4) センシティブな情報の除外
const fullUser = { id: 1, name: "Alice", password: "secret", token: "abc" };
const { password, token, ...publicUser } = fullUser;
console.log(publicUser); // { id: 1, name: "Alice" }

// 5) ネストを含むコピー(浅いコピーであることの確認)
const state = { count: 0, meta: { updatedBy: "system" } };
const next = { ...state, count: state.count + 1 };
next.meta.updatedBy = "user";
console.log(state.meta.updatedBy); // "user"(meta は共有)
JavaScript

まとめ

オブジェクトのスプレッド構文の核心は、「オブジェクトの中身を展開して、新しいオブジェクトを簡単に作る」ことです。{ ...obj } で浅いコピー、{ ...a, ...b } でマージ、{ ...old, changed: value } で“一部だけ変えた新しいオブジェクト”を作るのが基本パターンです。浅いコピーであること、順番で上書きの優先度が変わること、削除はできないので分割代入の ...rest と組み合わせて除外すること。このあたりを押さえると、初心者でも「元データを壊さずに、安全で読みやすい ES6+ のオブジェクト操作」がスムーズに書けるようになります。

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