Object.assign()を使ったコピーいろいろ

#Object.assign()を使ったコピーいろいろ

Object.assign()を使ったコピーについて調べたことを整理する。

##Object.assign(target, ...sources)
> 1個以上のソースオブジェクトについて、その直接所有(own)で列挙可能なプロパティの値をすべてターゲットオブジェクトにコピーして、新しいオブジェクトを生成します。

第一引数に指定したオブジェクトに第二引数以降の全てのオブジェクトのプロパティを第一引数に指定したオブジェクトにコピーしてくれる。

#####第一引数に無名オブジェクトを指定することで、コピーした内容の新規オブジェクトを作成できる。

```javascript
let obj1 = {a:1, b:2};
let obj2 = Object.assign({}, obj1);
console.log(obj2);	// {a:1, b:2}
```

#####第一引数に定義済みのオブジェクトを指定することで、第一引数のオブジェクトにマージできる。

```javascript
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} 第一引数にない項目がマージされる　同一キーの項目は後者の値で上書きされる
```

#####第二引数のプロパティにオブジェクトがネストされている場合、そのオブジェクトはオブジェクト参照がコピーされる。

```javascript
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がオブジェクト参照のため参照元の値が変わっている
```

#####ディープコピーをしたい場合、工夫が必要。

```javascript
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は更新されていない
```

プロトタイプチェーン上のプロパティはコピーされない。

```javascript
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はない
```

#####列挙可能でないプロパティはコピーされない。

```javascript
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}
```

#####アクセサディスクリプタはデータディスクリプタとしてコピーされる。

```javascript
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がアクセサディスクリプタである
```

#####アクセサディスクリプタとしてコピーする場合、工夫が必要。

```javascript
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