オブジェクトのスプレッド構文とは何か
オブジェクトのスプレッド構文は、{ ...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どちらを優先したいかを、順番でハッキリ表現するようにしましょう。
プロパティの「削除」はできない(除外は分割代入で)
スプレッドは「展開する」「上書きする」だけで、「削除」はできません。特定のプロパティを除外したいときは、
- 分割代入で「使わないキーだけ名前を取り出して捨てる」
- 残りを
...restにまとめる - それをスプレッドで展開する
という手順を取ります。
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+ のオブジェクト操作」がスムーズに書けるようになります。
