#Object.assign()を使ったコピーいろいろ
Object.assign()を使ったコピーについて調べたことを整理する。
##Object.assign(target, ...sources)
1個以上のソースオブジェクトについて、その直接所有(own)で列挙可能なプロパティの値をすべてターゲットオブジェクトにコピーして、新しいオブジェクトを生成します。
第一引数に指定したオブジェクトに第二引数以降の全てのオブジェクトのプロパティを第一引数に指定したオブジェクトにコピーしてくれる。
#####第一引数に無名オブジェクトを指定することで、コピーした内容の新規オブジェクトを作成できる。
let obj1 = {a:1, b:2};
let obj2 = Object.assign({}, obj1);
console.log(obj2); // {a:1, b:2}
#####第一引数に定義済みのオブジェクトを指定することで、第一引数のオブジェクトにマージできる。
let obj1 = {a:1, b:2};
let obj2 = {b:3, c:4};
Object.assign(obj1, obj2);
console.log(obj1); // {a:1, b:3, c:4} 第一引数にない項目がマージされる 同一キーの項目は後者の値で上書きされる
#####第二引数のプロパティにオブジェクトがネストされている場合、そのオブジェクトはオブジェクト参照がコピーされる。
let obj1 = {a:1, b:2};
let obj2 = {b:3, c:4, d:{a:5, b:6}}; // dはオブジェクト
Object.assign(obj1, obj2);
console.log(obj1); // {a:1, b:3, c:4, d:{a:5, b:6}}
obj1.d.a = 10; // obj1.d.aの値を更新
console.log(obj1); // {a:1, b:3, c:4, d:{a:10, b:6}}
console.log(obj2); // {b:3, c:4, d:{a:10, b:6}} ojb1.dがオブジェクト参照のため参照元の値が変わっている
#####ディープコピーをしたい場合、工夫が必要。
let obj1 = {a:1, b:2};
let obj2 = {b:3, c:4, d:{a:5, b:6}}; // dはオブジェクト
Object.assign(obj1, JSON.parse(JSON.stringify(obj2))); // JSON文字列に変換、オブジェクトに戻すことでdが別オブジェクトとなる
console.log(obj1); // {a:1, b:3, c:4, d:{a:5, b:6}}
obj1.d.a = 10; // obj1.d.aの値を更新
console.log(obj1); // {a:1, b:3, c:4, d:{a:10, b:6}}
console.log(obj2); // {b:3, c:4, d:{a:5, b:6}} obj2.dは更新されていない
プロトタイプチェーン上のプロパティはコピーされない。
let obj1 = {a:1, b:2};
let obj2 = Object.create({c:3}, {}); // createの第一引数にプロトタイプを指定
Object.assign(obj1, obj2);
console.log(obj1); // {a:1, b:2} プロトタイプのプロパティcはない
#####列挙可能でないプロパティはコピーされない。
let obj1 = {a:1, b:2};
let obj2 = Object.create({}, {c:{value:3, enumerable:false}}); // プロパティcのenumerableをfalseにすることで列挙可能でないプロパティとなる ちなみにenumerableはデフォルトfalse
Object.assign(obj1, obj2);
console.log(obj1); // {a:1, b:2}
#####アクセサディスクリプタはデータディスクリプタとしてコピーされる。
let obj1 = {a:1, b:2};
let obj2 = Object.create({}, {c:{value:0, writable:true}, d:{get:function(){return this.c;}, set:function(p){this.c = p+1;}, enumerable:true}}); // dをアクセサディスクリプタとして定義
Object.assign(obj1, obj2);
obj1.d = 3;
console.log(obj1.d); // 3
obj2.d = 3;
console.log(obj2.d); // 4 setterが呼び出されているため引数+1となる
console.log(Object.getOwnPropertyDescriptors(obj1)); // Object { a: Object { value: 1, writable: true, enumerable: true, configurable: true }, b: Object { value: 2, writable: true, enumerable: true, configurable: true }, d: Object { value: 3, writable: true, enumerable: true, configurable: true } } dがデータディスクリプタである
console.log(Object.getOwnPropertyDescriptors(obj2)); // Object { c: Object { value: 4, writable: true, enumerable: false, configurable: false }, d: Object { get: function(){return this.c;}, set: function(p){this.c = p+1;}, enumerable: true, configurable: false } } dがアクセサディスクリプタである
#####アクセサディスクリプタとしてコピーする場合、工夫が必要。
let obj1 = {a:1, b:2};
let obj2 = Object.create({}, {c:{value:0, writable:true}, d:{get:function(){return this.c;}, set:function(p){this.c = p+1;}, enumerable:true}}); //
Object.defineProperties(obj1, Object.getOwnPropertyDescriptors(obj2));
obj1.d = 3; // Object.assignではなくObject.definePropertiesを使い第一引数にコピー先オブジェクト、第二引数にObject.getOwnPropertyDescriptorsでコピー元オブジェクトを指定しコピー元のアクセサディスクリプタをコピーする
console.log(obj1.d); // 4 setterが呼び出されているため引数+1となる
obj2.d = 3;
console.log(obj2.d); // 4 setterが呼び出されているため引数+1となる
console.log(Object.getOwnPropertyDescriptors(obj1)); // Object { a: Object { value: 1, writable: true, enumerable: true, configurable: true }, b: Object { value: 2, writable: true, enumerable: true, configurable: true }, c: Object { value: 0, writable: true, enumerable: false, configurable: false }, d: Object { get: function(){return this.c;}, set: function(p){this.c = p+1;}, enumerable: true, configurable: false } } dがアクセサディスクリプタである ちなみに、Object.definePropertiesのコピーでは列挙可能でないプロパティもコピーされる
console.log(Object.getOwnPropertyDescriptors(obj2)); // Object { c: Object { value: 4, writable: true, enumerable: false, configurable: false }, d: Object { get: function(){return this.c;}, set: function(p){this.c = p+1;}, enumerable: true, configurable: false } } dがアクセサディスクリプタである
その他のやり方はあると思うが、Objectのメソッドのみで縛って考えてみた。
色々なやり方があるので、それぞれの方法の特色を生かして実装したい。
以下、参考としたサイト。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Object/assign