Object.assign() は第1引数を書き換えて返します。後ろが勝つこと、複製が1段しか効かないこと、 getter が値に変わること、freeze() も1段しか凍らないことを実行して確かめます。
Object.assign() は、第1引数そのものを書き換えて、それを返します。
example.js
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));
出力書き換えて実行できます
{"a":9,"b":2}
true
{"a":9,"b":2}
example.js
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);
出力書き換えて実行できます
{"a":1,"b":2}
{"a":1}
false
渡した target が変わっています。 ここが最大の注意点です。
- 後ろに書いたものが勝ちます
- 返るのは新しいオブジェクトではなく、第1引数そのもの
元を壊したくないなら先頭に空を置く
javascript
const base = { deep: { n: 1 } };
const copied = Object.assign({}, base);
copied.deep.n = 99;
console.log(base.deep.n);
99
example.js
const base = { deep: { n: 1 } };
const copied = structuredClone(base);
copied.deep.n = 99;
console.log(base.deep.n, copied.deep.n);
出力書き換えて実行できます
1 99
{} を先頭に置くと、base は無傷です。
いまは { ...base, b: 2 } と書くほうが短く、意図も伝わります。
→ スプレッド構文と残余
Object.assign() がいま必要なのは、「既にあるオブジェクトを書き換えたい」ときだけです。それ以外はスプレッドで足ります。
複製は1段しか効かない
example.js
console.log(JSON.stringify(Object.assign({}, { a: 1 }, null, undefined, { b: 2 })));
出力書き換えて実行できます
{"a":1,"b":2}
example.js
try {
Object.assign(null, { a: 1 });
} catch (error) {
console.log(error.name + ': ' + error.message);
}
出力書き換えて実行できます
TypeError: Cannot convert undefined or null to object
copied は別のオブジェクトですが、deep は同じものを指しています。
これを浅いコピーと呼びます。スプレッドでも同じです。
深く複製するなら structuredClone() です。
example.js
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);
出力書き換えて実行できます
{"own":2}
1
false false
example.js
const source = {
get computed() {
return 1 + 1;
},
};
const copied = Object.assign({}, source);
console.log(Object.getOwnPropertyDescriptor(copied, 'computed'));
出力書き換えて実行できます
{ value: 2, writable: true, enumerable: true, configurable: true }
null や undefined は無視される
example.js
const frozen = Object.freeze({ a: 1, deep: { n: 1 } });
frozen.a = 9;
frozen.b = 9;
console.log(JSON.stringify(frozen));
出力書き換えて実行できます
{"a":1,"deep":{"n":1}}
example.js
'use strict';
const frozen = Object.freeze({ a: 1 });
try {
frozen.a = 9;
} catch (error) {
console.log(error.name + ': ' + error.message);
}
出力書き換えて実行できます
TypeError: Cannot assign to read only property 'a' of object '#<Object>'
落ちません。 条件によって渡すものが消えても、そのまま通ります。
ただし第1引数が null なら落ちます。そこだけは対象になれません。
javascript
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));
99
true false
→ TypeError: Cannot convert undefined or null to object
入るのは「自分の・列挙できる」ものだけ
- 受け継いだものは入らない
- 列挙できないものは入らない
Symbol の鍵は入る(Object.keys() とはここだけ違う)
→ Object.keys() / values() / entries()
get は値に変わる
呼ばれた結果が、ただの値として入ります。
get のまま持っていきたいなら
Object.defineProperties() と Object.getOwnPropertyDescriptors() を使ってください。
Object.freeze() — 変えられなくする
書き換えも追加も黙って無視されます。
strict モードでは、無視ではなく落ちます。
import / export のあるファイルは自動的に strict なので、
実務では落ちるほうが普通です。
freeze も1段しか効かない
外側は凍っていますが、中の入れ子は凍っていません。
深く凍らせたいなら、自分で辿って凍らせる必要があります。
まとめ
Object.assign() は第1引数を書き換えて、それを返す
- 元を壊したくないなら
{} を先頭に置く。ふだんはスプレッドで足りる
- 後ろが勝つ。
null / undefined は無視される
- 複製も凍結も1段だけ。 深くしたいなら
structuredClone() か自分で辿る
get は呼ばれた結果の値になる
freeze() は strict では黙って無視ではなく落ちる