---
type: reference
language: javascript
slug: object/assign
title: "Object.assign() と Object.freeze()"
title_tag: "JavaScript の Object.assign() — 上書きと浅いコピーの罠"
summary: >
  Object.assign() は第1引数を書き換えて返します。後ろが勝つこと、複製が1段しか効かないこと、
  getter が値に変わること、freeze() も1段しか凍らないことを実行して確かめます。
description: >
  Object.assign() は渡した相手そのものを書き換えます。空のオブジェクトを先頭に置く理由、
  freeze() が中の入れ子を守らないことまで、実行しながら確かめられます。
status: published
difficulty: 2
minutes: 8

versions:
  verified: "Node 22.22.3"
  since: "ES2015"
  deprecated: null
  removed: null

sources:
  - title: "Object.assign — ECMAScript® 2026 Language Specification"
    url: "https://tc39.es/ecma262/#sec-object.assign"
  - title: "Object.freeze — ECMAScript® 2026 Language Specification"
    url: "https://tc39.es/ecma262/#sec-object.freeze"
  - title: "Object.assign() — MDN"
    url: "https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/assign"

terms: [浅いコピー, 破壊的, 凍結, 列挙可能]

links:
  related:
    - javascript/how-to/object/merge
    - javascript/reference/operator/spread
    - javascript/reference/object/keys

content_updated_at: 2026-09-08
published_at: 2026-09-08
---

`Object.assign()` は、**第1引数そのものを書き換えて、それを返します。**

```js run
const target = { a: 1 };
const result = Object.assign(target, { b: 2 }, { a: 9 });

console.log(JSON.stringify(result));
console.log(result === target);
console.log(JSON.stringify(target));
```
```output
{"a":9,"b":2}
true
{"a":9,"b":2}
```

**渡した `target` が変わっています。** ここが最大の注意点です。

- 後ろに書いたものが[key:勝ちます]
- 返るのは新しいオブジェクトではなく、**第1引数そのもの**

## 元を壊したくないなら先頭に空を置く

```js run
const base = { a: 1 };
const merged = Object.assign({}, base, { b: 2 });

console.log(JSON.stringify(merged));
console.log(JSON.stringify(base));
console.log(merged === base);
```
```output
{"a":1,"b":2}
{"a":1}
false
```

**`{}` を先頭に置くと、`base` は無傷です。**
いまは `{ ...base, b: 2 }` と書くほうが短く、意図も伝わります。
→ [スプレッド構文と残余](/ja/javascript/reference/operator/spread/)

[dim:`Object.assign()` がいま必要なのは、「既にあるオブジェクトを書き換えたい」ときだけです。それ以外はスプレッドで足ります。]

## 複製は1段しか効かない

```js bad
const base = { deep: { n: 1 } };
const copied = Object.assign({}, base);

copied.deep.n = 99;

console.log(base.deep.n);
```
```output
99
```

`copied` は別のオブジェクトですが、**`deep` は同じものを指しています。**
これを[type:浅いコピー]と呼びます。スプレッドでも同じです。

深く複製するなら `structuredClone()` です。

```js run
const base = { deep: { n: 1 } };
const copied = structuredClone(base);

copied.deep.n = 99;

console.log(base.deep.n, copied.deep.n);
```
```output
1 99
```

## `null` や `undefined` は無視される

```js run
console.log(JSON.stringify(Object.assign({}, { a: 1 }, null, undefined, { b: 2 })));
```
```output
{"a":1,"b":2}
```

**落ちません。** 条件によって渡すものが消えても、そのまま通ります。

ただし[bad:第1引数が null なら落ちます]。そこだけは対象になれません。

```js run
try {
  Object.assign(null, { a: 1 });
} catch (error) {
  console.log(error.name + ': ' + error.message);
}
```
```output
TypeError: Cannot convert undefined or null to object
```

→ [TypeError: Cannot convert undefined or null to object](/ja/javascript/errors/cannot-convert-undefined-or-null/)

## 入るのは「自分の・列挙できる」ものだけ

```js run
const sym = Symbol('s');
const source = Object.create({ inherited: 1 });

source.own = 2;
source[sym] = 3;
Object.defineProperty(source, 'hidden', { value: 4, enumerable: false });

const copied = Object.assign({}, source);

console.log(JSON.stringify(copied));
console.log(Object.getOwnPropertySymbols(copied).length);
console.log('hidden' in copied, 'inherited' in copied);
```
```output
{"own":2}
1
false false
```

- 受け継いだものは[bad:入らない]
- 列挙できないものは[bad:入らない]
- **`Symbol` の鍵は入る**（`Object.keys()` とはここだけ違う）

→ [Object.keys() / values() / entries()](/ja/javascript/reference/object/keys/)

## `get` は値に変わる

```js run
const source = {
  get computed() {
    return 1 + 1;
  },
};

const copied = Object.assign({}, source);

console.log(Object.getOwnPropertyDescriptor(copied, 'computed'));
```
```output
{ value: 2, writable: true, enumerable: true, configurable: true }
```

**呼ばれた結果が、ただの値として入ります。**
`get` のまま持っていきたいなら
`Object.defineProperties()` と `Object.getOwnPropertyDescriptors()` を使ってください。

## `Object.freeze()` — 変えられなくする

```js run
const frozen = Object.freeze({ a: 1, deep: { n: 1 } });

frozen.a = 9;
frozen.b = 9;

console.log(JSON.stringify(frozen));
```
```output
{"a":1,"deep":{"n":1}}
```

書き換えも追加も**黙って無視されます。**

**strict モードでは、無視ではなく落ちます。**

```js run
'use strict';

const frozen = Object.freeze({ a: 1 });

try {
  frozen.a = 9;
} catch (error) {
  console.log(error.name + ': ' + error.message);
}
```
```output
TypeError: Cannot assign to read only property 'a' of object '#<Object>'
```

`import` / `export` のあるファイルは自動的に strict なので、
実務では[em:落ちるほうが普通]です。

### freeze も1段しか効かない

```js bad
const frozen = Object.freeze({ deep: { n: 1 } });

frozen.deep.n = 99;

console.log(frozen.deep.n);
console.log(Object.isFrozen(frozen), Object.isFrozen(frozen.deep));
```
```output
99
true false
```

外側は凍っていますが、**中の入れ子は凍っていません。**
深く凍らせたいなら、自分で辿って凍らせる必要があります。

## まとめ

- `Object.assign()` は**第1引数を書き換えて、それを返す**
- 元を壊したくないなら `{}` を先頭に置く。ふだんは**スプレッドで足りる**
- 後ろが勝つ。`null` / `undefined` は無視される
- **複製も凍結も1段だけ。** 深くしたいなら `structuredClone()` か自分で辿る
- `get` は**呼ばれた結果の値**になる
- `freeze()` は strict では[bad:黙って無視ではなく落ちる]
