オブジェクトを結合する

結合はスプレッド構文が最短です。ただしコピーされるのは1階層目だけで、入れ子は元と共有されます。undefined で上書きされる罠と、深く複製する方法までまとめます。

実行して検証済み処理系Node 22.22.3対応ES2018難易度●●○○○目安9更新

答え

スプレッド構文で並べます。後ろが勝ちます。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
{ name: 'あかり', role: 'admin' } { name: 'あかり', role: 'user' }
javascript
const base = { name: 'あかり', role: 'user' };
const patch = { role: 'admin' };

const merged = Object.assign(base, patch);

console.log(merged);
console.log(base);
console.log(merged === base);
{ name: 'あかり', role: 'admin' } { name: 'あかり', role: 'admin' } true

元のオブジェクトは変わりません。新しいオブジェクトが作られます。

上のコードは書き換えて実行できます。{ ...patch, ...base } と順序を逆にしてみてください。

Object.assign との違い

Object.assign()第1引数を書き換えます

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
{ name: 'あかり', role: 'user' } false
javascript
const base = { name: 'あかり', role: 'user' };
const patch = { role: undefined };

console.log({ ...base, ...patch });
{ name: 'あかり', role: undefined }

base まで変わりました。戻り値も同じオブジェクトです。

書き換えたくないなら、空のオブジェクトを先頭に置きます。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
{ name: 'ゆうと', role: 'user' }
javascript
const base = { name: 'あかり', tags: ['a', 'b'] };

const copy = { ...base };

copy.tags.push('c');

console.log(copy.tags);
console.log(base.tags);
console.log(copy.tags === base.tags);
[ 'a', 'b', 'c' ] [ 'a', 'b', 'c' ] true
元を書き換えるか 書き方
{ ...a, ...b } 書き換えない 短い。ふだんはこちら
Object.assign(a, b) a を書き換える 意図して書き換えるときだけ
Object.assign({}, a, b) 書き換えない スプレッドと同じ結果

undefined でも上書きされる

値が undefined でも、キーがあれば上書きします。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
あかり ゆうと true
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
[ 'a', 'b' ] { n: 1 } [ 'a', 'b', 'c' ] { n: 99 }

role が消えたのではなく、undefined塗りつぶされました

フォームの入力をそのまま重ねると、未入力の項目が既定値を壊します。 先に undefined を落としてください。

Object.entries()Object.fromEntries() の詳しい話は Object.keys() / values() / entries() にあります。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
true true true false
javascript
console.log(structuredClone({ fn: () => 1 }));
DataCloneError: () => 1 could not be cloned.

null は「意味のある値」として扱う場面が多いので、上では落としていません。落とすかどうかは仕様次第です。

コピーされるのは1階層目だけ

これがいちばん多い事故です。入れ子のオブジェクトは元と共有されます浅いコピー)。

javascript
const src = {
d: new Date('2026-09-07'),
u: undefined,
n: NaN,
m: new Map([['k', 1]]),
};

console.log(JSON.parse(JSON.stringify(src)));
{ d: '2026-09-07T00:00:00.000Z', n: null, m: {} }
javascript
const base = { name: 'あかり', config: { theme: 'dark', size: 12 } };
const patch = { config: { size: 16 } };

console.log({ ...base, ...patch });
{ name: 'あかり', config: { size: 16 } }

copy だけを触ったつもりが、base も変わりました。 tags は同じ配列を参照しているからです。

1階層目は別物になっています。そこだけ見て「複製できた」と思うと踏みます。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
{ name: 'あかり', config: { theme: 'dark', size: 16 } }
example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
{ a: 1, nested: { x: 1, y: { z: 9 }, w: 2 } } { a: 1, nested: { x: 1, y: { z: 1 } } }

深く複製する

structuredClone() を使います。入れ子も全部たどって複製します。

example.js
出力書き換えて実行できます
Ctrl+Enter でも実行JS · UTF-8
[ '1', '2', 'b', 'a' ] { '1': 4, '2': 2, b: 1, a: 3 }

元に影響しません。 DateMap も複製できます。

関数は複製できません。 入っていると例外になります。

structuredClone は Node 17 / Chrome 98 / Safari 15.4 以降で使えます。

JSON を経由する方法は勧めない

古くからある書き方ですが、落ちるものが多すぎます。

Date文字列に、NaNnullに、Map空オブジェクトになり、 undefined のキーは消えました

structuredClone() を使ってください。

入れ子を混ぜたいとき

スプレッドを重ねても、入れ子は丸ごと差し替わります。

theme消えました。混ぜたいなら、その階層でもスプレッドします。

階層が深いなら、再帰で書きます。

配列をどう扱うか(置き換えるか繋ぐか)は仕様によって変わります。上は置き換えています。

キーの順序

数字に見えるキーだけ先に来ます。並べた順にはなりません。

121文字目に来て、そのあと文字のキーが書いた順で並びます。 順序に依存する処理を書かないでください。 必要なら Map か配列を使います。

JSON.parse(JSON.stringify(...)) で複製すると何が失われるかは、JSON のページに一覧があります。 → JSON.parse() と JSON.stringify()

この記事の根拠

  1. Object.assign — ECMAScript® 2026 Language Specificationtc39.es
  2. structuredClone() — MDNdeveloper.mozilla.org
  3. スプレッド構文 — MDNdeveloper.mozilla.org

掲載しているコード例は、公開前に読み手が押したときに動くのと同じもので実行して出力を突き合わせています。結果はリポジトリに残しています。

このページの原文(Markdown)