オブジェクトコピーとスプレッド構文の関係
オブジェクトのスプレッド構文は、{ ...obj } のように書いて「オブジェクトの中身(プロパティ)を展開して、新しいオブジェクトを作る」書き方です。ここが重要です:const copy = original; は “同じオブジェクトを指す別名” を作るだけですが、const copy = { ...original }; は “中身が同じ別オブジェクト” を作ります。元のオブジェクトを壊さずに扱いたいなら、スプレッド構文でコピーを作るのが基本になります。
const user = { id: 1, name: "Alice" };
const alias = user; // 同じオブジェクトへの別名
const copy = { ...user }; // 中身をコピーした別オブジェクト
alias.name = "Bob";
console.log(user); // { id: 1, name: "Bob" }
console.log(copy); // { id: 1, name: "Alice" } ← 影響を受けていない
JavaScript代入とスプレッドコピーの違いをしっかり理解する
代入(=)は「同じ箱に別ラベルを貼る」イメージです。どちらから触っても同じ中身が変わります。
const config = { theme: "light" };
const same = config; // 参照コピー
same.theme = "dark";
console.log(config.theme); // "dark"
console.log(same.theme); // "dark"
console.log(config === same); // true(完全に同じもの)
JavaScriptスプレッドコピーは「中身を複製して別の箱を作る」イメージです。ここが重要です:{ ...obj } で作った方は「別物」なので、一方を変更しても、もう一方には影響しません。
const config = { theme: "light" };
const copy = { ...config }; // 浅いコピー
copy.theme = "dark";
console.log(config.theme); // "light"(元はそのまま)
console.log(copy.theme); // "dark"
console.log(config === copy); // false(別オブジェクト)
JavaScript「元を壊さずに一部だけ変えたい」という場面では、必ず { ...obj } でコピーを作ってから変更する、という習慣がとても重要です。
浅いコピーという性質(ネストした中身の扱い)
スプレッド構文によるコピーは「浅いコピー(shallow copy)」です。これは「一段目のプロパティだけ新しく作り、プロパティの中に入っているオブジェクトや配列は“参照を共有する”」という意味です。
const state = {
id: 1,
profile: { name: "Alice", age: 20 }
};
const copy = { ...state };
copy.profile.name = "Bob";
console.log(state.profile.name); // "Bob"
console.log(copy.profile.name); // "Bob"
console.log(state === copy); // false(外側は別)
console.log(state.profile === copy.profile); // true(内側は同じ)
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順番を逆にすると結果が変わるのも確認しておきましょう。
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「デフォルト → 上書きする実際の値」の順に並べる、というルールにしておくと迷いません。
分割代入の rest と組み合わせて「除外コピー」
スプレッドは「展開(コピー・マージ・上書き)」の機能で、プロパティを“削除”することはできません。特定のプロパティを除いたコピーを作りたいときは、オブジェクトの分割代入の ...rest と組み合わせます。
const user = {
id: 1,
name: "Alice",
password: "secret",
token: "abc123"
};
// password と token を取り除いた“公開用”オブジェクト
const { password, token, ...publicUser } = user;
console.log(publicUser);
// { id: 1, name: "Alice" }
JavaScriptここが重要です:
- 分割代入の
...publicUser(左側)は「残りをまとめて受け取る」 - スプレッドの
{ ...publicUser }(右側)は「その中身を展開して新しいオブジェクトを作る」
「機密情報を除いたオブジェクトを別で扱う」ような場面で非常に役立ちます。
関数の中での「安全なオブジェクト操作」
外から渡されたオブジェクトを、関数の中で直接書き換えると、呼び出し側が予想していない動きになりがちです。ここが重要です:関数内では「まずスプレッドでコピーを作ってから変更し、新しいオブジェクトを返す」習慣をつけると、安全でバグの少ないコードになります。
function enableDebug(config) {
// 引数の config は触らず、新しいオブジェクトを返す
return {
...config,
debug: true
};
}
const base = { theme: "light", debug: false };
const updated = enableDebug(base);
console.log(base); // { theme: "light", debug: false }
console.log(updated); // { theme: "light", debug: true }
JavaScript設定マージもよくあるパターンです。
const defaultOptions = {
method: "GET",
timeout: 3000
};
function request(url, options = {}) {
const opts = {
...defaultOptions, // デフォルト
...options // 呼び出し側の上書き
};
console.log(url, opts);
}
request("/api/users", { timeout: 5000 });
// timeout だけ 5000 に変わる
JavaScriptよくある落とし穴と注意点
一つ目は「浅いコピーであること」を忘れて、ネストした中身まで別になっていると勘違いすることです。オブジェクトや配列の中身を変えると元にも影響するので、「変えていいかどうか」を意識してから変更してください。
const state = {
user: { name: "Alice" }
};
const copy = { ...state };
copy.user.name = "Bob";
console.log(state.user.name); // "Bob"(共有)
JavaScript二つ目は、「順番で上書きの優先度が変わる」ことを軽く考えてしまうことです。どちらを優先したいかを常に意識し、「デフォルト → 上書き」と並べるのを癖にしましょう。
三つ目は、「削除したいのにスプレッドだけでなんとかしようとしてしまう」ことです。削除したいプロパティがあるなら、分割代入で取り出して捨て、残りを ...rest でまとめてから使う、という二段構えが必要です。
例題で理解を固める
// 1) ユーザー情報をコピーして表示用フラグを追加
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 }
// 2) 設定の一部上書き(lang だけ変更)
const baseCfg = { theme: "dark", lang: "ja", debug: false };
const newCfg = { ...baseCfg, lang: "en" };
console.log(newCfg);
// { theme: "dark", lang: "en", debug: false }
// 3) 機密情報を除外したレスポンス作成
const fullUser = { id: 1, name: "Alice", password: "secret", token: "xxx" };
const { password, token, ...safeUser } = fullUser;
console.log(safeUser);
// { id: 1, name: "Alice" }
// 4) ネストを含む状態を更新(外側だけ新しくする)
const state = {
count: 0,
meta: { updatedBy: "system" }
};
// count だけ増やした新しい state
const next = { ...state, count: state.count + 1 };
console.log(state); // { count: 0, meta: { updatedBy: "system" } }
console.log(next); // { count: 1, meta: { updatedBy: "system" } }
// 5) デフォルトオプションに部分的な設定をマージ
const defaults = { method: "GET", timeout: 3000, cache: "no-cache" };
function makeOptions(userOptions = {}) {
return { ...defaults, ...userOptions };
}
console.log(makeOptions({ timeout: 1000 }));
// { method: "GET", timeout: 1000, cache: "no-cache" }
JavaScriptまとめ
オブジェクトコピーにおけるスプレッド構文の核心は、「{ ...obj } で元と同じ中身を持つ“別オブジェクト”を作り、元を壊さずに安全に扱う」ことです。代入は同じものへの別名、スプレッドは浅いコピー。ネストした中身は共有される点に注意しつつ、{ ...old, changed: value } で一部だけ変えた新バージョンを作る、分割代入の ...rest と組み合わせて「除外コピー」を作る、関数内ではコピーしてから変更して返す、というパターンを身につけると、初心者でもバグの少ない ES6+ のオブジェクト操作が書けるようになります。
