ゆったりWeb手帳

オブジェクトのコピーのやっかいなところ

オブジェクトのコピー

JavaScriptではオブジェクトのコピーに気をつかいます。
変数をコピーする場合は書いた通りですが、オブジェクトではそう簡単ではないのです。

JavaScript
let a = 0
console.log(a) // 0
let b = a
b = 1
console.log(a) // 0

bという変数にaを代入し、bを書き換えても、aの中身は変わりません。

これをオブジェクトでやってみます。

JavaScript
let obj1 = { a: 0 }
console.log(obj1.a) // 0
let obj2  = obj1
obj2.a = 1
console.log(obj1.a) // 1

このように、オブジェクトのコピー先でプロパティの値を変更するとコピー元のオブジェクトのプロパティの値も変更されます。

これは、オブジェクトの場合はコピーではなく、オブジェクトへのリンクを共有しているだけだからです。

上記の例では{ a: 0 }というオブジェクトの位置をobj1が保持し、それをobj2にコピーしているので、obj1.aobj2.aも同じオブジェクトを指します。

ちなみにオブジェクトはletだろうとconstだろうとプロパティの書き換えはできます笑

Object.assign()を使う方法

オブジェクトを変数と同じようにコピーするにはObject.assign()を使ってオブジェクトを新しく作成する必要があります。 Object.assign()はES6以降で使用できます。

JavaScript
let obj1 = { a: 0 }
console.log(obj1.a) // 0
let obj2  = Object.assign({}, obj1)
obj2.a = 1
console.log(obj1.a) // 0

Object.assign() - JavaScript | MDN

ただ、Object.assign()にも問題があって、コピー元のオブジェクトを展開してはくれないのです。 プロパティに配列やオブジェクトを含む多層のオブジェクトだと、2層目以下はまたメモリ位置のみのコピーになります。

JavaScript
let obj1 = { a: [0] }
console.log(obj1.a[0]) // 0
let obj2  = Object.assign({}, obj1)
obj2.a[0] = 1
console.log(obj1.a[0]) // 1

こうなってしまいます。

これをシャローコピーというそうです。

調べてみるとJavaScriptにディープコピーしてくれる関数はまだ用意されていないようです。

JSON.parseとJSON.stringifyを使う方法

JavaScript
let obj1 = { a: [0] }
console.log(obj1.a[0]) // 0
let obj2 = JSON.parse(JSON.stringify(obj1))
obj2.a[0] = 1
console.log(obj1.a[0]) // 0

JSON.parseJSON.stringifyは一度jsonに展開してまたオブジェクトに戻す関数です。ただ、プロパティの値にfunctionやundefinedがあるとそのプロパティそのものが消えてしまうので注意してください。

Object.freeze()について

ちなみにオブジェクトはletだろうとconstだろうとプロパティの書き換えはできます笑 あとからコピー先でプロパティの書き換えをされても変更しないようにするにはObject.freeze()を使います。

JavaScript
let obj1 = { a: 0 }
Object.freeze(obj1)

Object.freeze() - JavaScript | MDN