JavaScript | 基礎構文:オブジェクト – スプレッド構文による浅いコピー

JavaScript JavaScript
スポンサーリンク

スプレッド構文とは何か

スプレッド構文 { ...obj } は、既存のオブジェクトの「自分が持っている列挙可能なプロパティ」を表面だけコピーして新しいオブジェクトを作る書き方です。重要なのは「浅いコピー」だという点で、入れ子のオブジェクトや配列は“参照”がそのまま引き継がれます。これにより、元を壊さずに「一段目だけを複製・差し替え」するのに向いています。


浅いコピーの意味と挙動

浅いコピーは「一段目のみ別物、奥は共有」です。数値や文字列などのプリミティブ値はそのまま複製されますが、入れ子のオブジェクト・配列は同じ参照を持つため、深い箇所を変更すると元にも影響します。プロトタイプはコピーされず、作られるのは常に「プレーンな新しいオブジェクト」です。


基本例:オブジェクトを複製して安全に更新する

例1:表面だけコピー

const original = { name: "太郎", age: 20 };
const copy = { ...original };

copy.age = 21;
console.log(original.age); // 20(元は不変)
console.log(copy.age);     // 21(新しい値)
JavaScript

例2:コピー+上書き(右側が優先)

const original = { name: "太郎", age: 20 };
const updated = { ...original, age: 22, city: "東京" };

console.log(updated); // { name: "太郎", age: 22, city: "東京" }
JavaScript

ここが重要です:同じキーが重なる場合は「右側が勝つ(後勝ち)」。上書きの意図を右側にまとめると読みやすくなります。


ネストの落とし穴(深い部分は共有される)

例3:入れ子は別途コピーが必要

const user = { profile: { city: "東京", zip: "100-0000" } };
const shallow = { ...user };

shallow.profile.city = "大阪";
console.log(user.profile.city);   // 大阪(内側は共有されている)
JavaScript

ここが重要です:入れ子を安全に更新したいときは「入れ子もスプレッドで複製してから上書き」します。

const safe = { 
  ...user,
  profile: { ...user.profile, city: "大阪" }
};
console.log(user.profile.city); // 東京(元は不変)
console.log(safe.profile.city); // 大阪
JavaScript

プロトタイプ・属性・getterの扱い(一歩深掘り)

  • コピー範囲: 自分自身の列挙可能なプロパティ(文字列キーと Symbolキー)が対象です。継承(プロトタイプ)上のプロパティはコピーされません。
  • プロパティ属性: writable/enumerable/configurable といった属性は維持されず、結果は「通常プロパティ(値)」として複製されます。
  • getter/setter: コピー時は「値が読み取られて」結果が入ります。アクセサ自体は複製されません。
const source = {
  get price() { return 100; }
};
const copy = { ...source };
console.log(copy.price); // 100(値は入るが、getterではない)
JavaScript

よくある注意点(挙動を正しく掴む)

例4:null/undefinedは展開不可

// const bad = { ...null };      // TypeError
// const bad2 = { ...undefined }; // TypeError
JavaScript

ここが重要です:展開する対象は「オブジェクト」である必要があります。安全に扱うなら条件分岐やデフォルトを使います。

const maybe = null;
const merged = { ...(maybe || {}), a: 1 };
console.log(merged); // { a: 1 }
JavaScript

例5:配列や特殊オブジェクトの中身は“参照”のまま

const original = { arr: [1, 2], date: new Date() };
const copy = { ...original };

copy.arr.push(3);
console.log(original.arr); // [1, 2, 3](配列は共有)
console.log(copy.date === original.date); // true(同じ参照)
JavaScript

ここが重要です:深い独立が必要なら structuredClone や要素単位のコピーを検討します。


実践パターン:イミュータブル更新の基本形

UIや状態管理では「元を壊さず新しいオブジェクトにする」が定石です。スプレッド構文はその中心的ツールです。

例6:差分更新を積み重ねる

const user = { name: "太郎", profile: { city: "東京" } };

// 名前だけ変更
const u1 = { ...user, name: "花子" };

// 市だけ変更(入れ子もコピーしてから)
const u2 = { 
  ...u1, 
  profile: { ...u1.profile, city: "大阪" } 
};

console.log(user.name, user.profile.city); // 太郎 東京(元は不変)
console.log(u2.name, u2.profile.city);     // 花子 大阪(新しい状態)
JavaScript

ここが重要です:どの層を「浅く」どの層を「深く」コピーするかを意識し、必要最小限の複製で読みやすさと性能のバランスを取りましょう。


まとめ

スプレッド構文 { ...obj } は「一段目だけを複製する浅いコピー」で、右側が勝つ上書きルールと相性が良く、イミュータブルな更新に最適です。入れ子は参照が共有されるため、深い更新が必要な箇所は入れ子もスプレッドで複製してから差し替えます。プロトタイプやアクセサ、属性はそのまま保たれない点を理解し、null/undefinedは展開不可であることにも注意しましょう。この“浅くコピーして必要なところだけ深く差し替える”感覚が、壊れにくく予測可能なコードへの近道です。

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