30歳からのプログラミング

30歳無職から独学でプログラミングを開始した人間の記録。

オブジェクトの値をコピーするObject.assign()

JavaScriptでは、オブジェクトが格納されている変数を他の変数にコピーすると、そのオブジェクトへの参照がコピーされる。
そのため、元となるオブジェクトの値を変更すると、コピー先のオブジェクトにも変更が反映されてしまう。逆も然り。

let obj1 = {value:'hoge'};
let obj2 = obj1;
obj1.value = 'fuga';
console.log(obj2);  // { value: 'fuga' }

だがES2015で追加されたObject.assign()を使うと、参照ではなく値をコピーできる。
値が同じだけの全く別のオブジェクトなので、一方で行った変更の影響は、もう一方には及ばない。

let obj1 = {value: 'hoge'}
let obj2 = Object.assign({}, obj1);
console.log(obj2);  // { value: 'hoge' }
obj1.value = 'fuga';
console.log(obj1);  // { value: 'fuga' }
console.log(obj2);  // { value: 'hoge' }

Object.assign()の第一引数に空のオブジェクトを渡し、第二引数にコピー元のオブジェクトを渡すと、コピーされたオブジェクトを返す。
obj1obj2は別個のオブジェクトなので、obj1のプロパティを変更しても、obj2は影響を受けない。

注意点は、プロパティにオブジェクトがあると、そのオブジェクトは参照渡しになってしまう。
直下のプロパティをコピーしていく、というのがObject.assign()の挙動のようなので、そこにオブジェクトがあると、参照がコピーされてしまう。

let obj1 = {
    value: 'hoge',
    obj2: {
        num: 1
    }
};

let copy = Object.assign({}, obj1);
console.log(copy);  // { value: 'hoge', obj2: { num: 1 } }

obj1.value = 'fuga';
obj1.obj2.num = 9;
console.log(copy);  // { value: 'hoge', obj2: { num: 9 } }

valueは値がコピーされているが、obj2は参照渡しになっているのが確認できる。

Object.assign()の詳細

Object.assign()の本来の用途は、オブジェクトをマージすることである。その機能を利用することで、上記のようなオブジェクトのコピーにも使える、ということである。

第一引数に渡したオブジェクトに、第二引数以降のオブジェクトのプロパティを追加していく。

第一引数に渡したオブジェクトが変更される

let obj1 = {value:'hoge'};
let obj2 = {};
Object.assign(obj2, obj1);
console.log(obj2);  // { value: 'hoge' }

Object.assign()を実行した時点で、第一引数のオブジェクトは変更される。
実行前の状態は残らないので、注意が必要。
そして、変更後のオブジェクトが、そのまま戻り値になる。

対象となるのは、列挙可能な自身のプロパティ

マージされるのは、列挙可能(Enumerable)な自身のプロパティのみである。列挙不可能なものは、対象にならない。

let obj1 = {value:'hoge', num:1};

console.log(obj1.propertyIsEnumerable('value'));   // true
console.log(Object.assign({}, obj1));   // { value: 'hoge', num: 1 }

Object.defineProperty(obj1, 'value', {
    enumerable: false
});

console.log(obj1.propertyIsEnumerable('value'));   // false
console.log(Object.assign({}, obj1));   // { num: 1 }

valueenumerablefalseになるとマージされなくなっているのが分かる。

プロパティの内部属性であるenumerableについては、下記を参照。
プロパティの内部属性

競合した場合は上書きされていく

第一引数のオブジェクトに対して、第二引数以降のオブジェクトが順次マージされていくが、その過程で同名のプロパティがあった場合、上書きされていく。

let obj1 = {value: 'hoge'};
let obj2 = {value: 'fuga'};
let obj3 = {num: 1, bool: true};
let obj4 = {value: 'boo'};

Object.assign(obj1, obj2);
console.log(obj1);  // { value: 'fuga' }

obj1 = {value: 'hoge'};
console.log(obj1);  // { value: 'hoge' }
Object.assign(obj1, obj2, obj3);
console.log(obj1);  // { value: 'fuga', num: 1, bool: true }

obj1 = {value: 'hoge'};
console.log(obj1);  // { value: 'hoge' }
Object.assign(obj1, obj2, obj3, obj4);
console.log(obj1);  // { value: 'boo', num: 1, bool: true }

参考資料

Object.assign() - JavaScript | MDN