スプレッド構文の「上書き順序」とは何か
スプレッド構文では、... を使って配列・オブジェクトの中身を「左から右へ」順番に展開していきます。
ここが最重要ポイントです:
- オブジェクトの
{ ...a, ...b }では、あとに書いた...bが、前に書いた...aと同じキーを持っていた場合に上書きする - 配列の
[...a, ...b]では、「上書き」ではなく単に「後ろに続けて並ぶ」だけ(要素を上書きはしない)
つまり、「右側ほど優先度が高い」「後に出てきたものが勝つ」と覚えてください。
オブジェクトの上書き順序(ここを一番しっかり理解する)
基本:右側が左側を上書きする
const a = { x: 1, y: 2 };
const b = { y: 99, z: 3 };
const ab = { ...a, ...b };
console.log(ab); // { x: 1, y: 99, z: 3 }
const ba = { ...b, ...a };
console.log(ba); // { y: 2, z: 3, x: 1 }
JavaScript同じキー y が両方にありますが、
{ ...a, ...b }→a.yが最初に入り、その後b.yが上書き →y: 99{ ...b, ...a }→b.yが最初に入り、その後a.yが上書き →y: 2
ここが重要です:
「デフォルト → 上書きしたい値」の順に並べるのが定石です。
const defaultCfg = { theme: "light", lang: "ja" };
const userCfg = { lang: "en" };
const cfg = { ...defaultCfg, ...userCfg };
// lang は "en" に上書きされる
JavaScript「ベース+部分変更」での上書き順序
元の設定をベースに、一部だけ変えた新オブジェクトを作りたいときも同じルールです。
const config = {
theme: "light",
lang: "ja",
debug: false
};
// debug だけ true にしたい
const newConfig = {
...config,
debug: true
};
console.log(newConfig);
// { theme: "light", lang: "ja", debug: true }
JavaScriptもし順序を逆にすると、上書きが効きません。
const wrong = {
debug: true,
...config
};
console.log(wrong);
// { theme: "light", lang: "ja", debug: false } ← debug が false に戻ってしまう
JavaScriptここが重要です:
「あとに書いたものが勝つ」というルールを意識しながら、並べる順番で優先度を表現すること。
配列でのスプレッドと「順序」
配列では、「上書き」ではなく「順番に並ぶだけ」です。
ただし、ここでも「左から右に並ぶ」ことが意味を持ちます。
単純な結合と順番
const a = [1, 2];
const b = [3, 4];
const ab = [...a, ...b];
console.log(ab); // [1, 2, 3, 4]
const ba = [...b, ...a];
console.log(ba); // [3, 4, 1, 2]
JavaScript配列はキーではなく「位置」で意味を持つので、
どの配列を前・後ろに置くかで意味が変わることを意識します。
途中に値を挟むときの順番
const base = [1, 2];
const merged = [0, ...base, 99];
console.log(merged); // [0, 1, 2, 99]
JavaScript「0 → base の要素 → 99」という順に並ぶので、
結局「中身をどの順序で読みたいか」が、そのままコードの順序になります。
デフォルト値とユーザー入力のマージ(重要な実務パターン)
オプションオブジェクトのマージ
ライブラリや関数のオプションでよくやるパターンです。
const defaultOptions = {
method: "GET",
timeout: 3000,
cache: "no-cache"
};
function request(url, options = {}) {
const opts = {
...defaultOptions, // デフォルトを先に展開
...options // ユーザー指定で上書き
};
console.log(url, opts);
}
request("/api/users", { timeout: 5000, method: "POST" });
JavaScriptこの場合、
- method:
"GET"→"POST"に上書き - timeout:
3000→5000に上書き - cache:
"no-cache"(ユーザー指定がないのでそのまま)
という結果になります。
ここが重要です:
「上書きしてほしい側(ユーザー側)を後ろに置く」
これを守っていれば、まず大きく間違えません。
ネストしたオブジェクトでの上書き順序
外側と内側の二段構え
状態オブジェクトなどで、ネストした部分だけを変えたいときも、
スプレッド+上書き順序を意識して組み立てます。
const state = {
user: { name: "Alice", age: 20 },
ui: { theme: "light", lang: "ja" }
};
// ui.theme だけ "dark" に変えたい
const newState = {
...state, // まず state を展開
ui: {
...state.ui, // 次に ui を展開(デフォルト)
theme: "dark" // さらに theme を上書き
}
};
console.log(newState.ui);
// { theme: "dark", lang: "ja" }
JavaScript内側でも「ベース → 上書き」の順番を守っているのがポイントです。
もし順序を間違えると、意図した更新になりません。
const badState = {
...state,
ui: {
theme: "dark",
...state.ui
}
};
console.log(badState.ui);
// { theme: "light", lang: "ja" } ← state.ui が theme を上書きしてしまう
JavaScriptよくある落とし穴と対策(重要部分の深掘り)
「強い値」を前に書いてしまう
初心者がよくやるミスは、優先度の高い値を先に書いてしまうことです。
const strong = { debug: true };
const base = { debug: false };
const wrong = { ...strong, ...base };
console.log(wrong.debug); // false(負けてしまう)
const correct = { ...base, ...strong };
console.log(correct.debug); // true(勝つ)
JavaScript対策はシンプルで、「あとから書いた方が強い」というルールを常に思い出すことです。
「どっちが勝ってほしい?」と自問してから、順番を決めましょう。
複数回マージしているうちに順番を見失う
マージが何段階も重なると、「どこで何を上書きしているか」が見えづらくなります。
const a = { x: 1 };
const b = { x: 2 };
const c = { x: 3 };
const result = { ...a, ...b, ...c }; // 結局 x は 3
JavaScript対策としては:
- できるだけ「1回のマージ」にまとめる
- 「デフォルト → 中間 → 最終」のように、層ごとに変数名を分ける
などで、上書きの流れを見えるようにすることです。
配列を「上書きする」つもりで結合してしまう
配列はそもそも「上書き」ではなく「連結」なので、
オブジェクトと同じ感覚で「右側が勝つ」と思っていると勘違いを生みます。
const a = [1, 2];
const b = [3, 4];
const wrong = [...a, ...b]; // これは a の 1,2 が「消える」わけではない
JavaScriptここが重要です:
- オブジェクト:キーが同じなら右側が左側を上書き
- 配列:単純に後ろに続くだけ(上書きの概念はない)
としっかり分けて考えることです。
例題で理解を固める
// 1) デフォルト+ユーザー設定(ユーザー優先)
const defaultCfg = { theme: "light", lang: "ja", debug: false };
const userCfg = { lang: "en" };
const cfg = { ...defaultCfg, ...userCfg };
console.log(cfg);
// { theme: "light", lang: "en", debug: false }
// 2) ロールごとの権限マージ(後ろのロールが強い)
const roleUser = { read: true, write: false };
const roleAdmin = { write: true, delete: true };
const mergedRole = { ...roleUser, ...roleAdmin };
console.log(mergedRole);
// { read: true, write: true, delete: true }
// 3) ネストした設定の一部更新
const state = {
ui: { theme: "light", lang: "ja" },
data: { page: 1 }
};
const nextState = {
...state,
ui: { ...state.ui, theme: "dark" }
};
console.log(nextState.ui);
// { theme: "dark", lang: "ja" }
// 4) 間違った順序の例(意図した変更が消える)
const base = { timeout: 3000, retry: 1 };
const override = { timeout: 5000 };
const wrongOrder = { ...override, ...base };
console.log(wrongOrder.timeout); // 3000(負けてる)
const correctOrder = { ...base, ...override };
console.log(correctOrder.timeout); // 5000(勝ってる)
JavaScriptまとめ
スプレッド構文の「上書き順序」の核心は、「同じキーの場合、右側が左側を上書きする(後から書いたものが勝つ)」という一点です。
オブジェクトでは { ...default, ...override } の順に並べて「デフォルト → 上書き」の意図を表現し、ネストしたオブジェクトでも同じパターンを内側・外側で繰り返す。
配列は上書きではなく単に連結だ、という違いも押さえる。
このルールを明確に意識できていれば、初心者でもスプレッド構文を使った設定マージや状態更新で「なぜか値が戻る/消える」といった事故を大きく減らすことができます。
