teratail header banner
teratail header banner
質問するログイン新規登録

回答編集履歴

1

コピーメソッドを、クラスメソッドもコピーできるものに修正、参照についての説明に補足追記

2020/02/07 03:33

投稿

miyabi_pudding
miyabi_pudding

スコア9567

answer CHANGED
@@ -1,6 +1,7 @@
1
1
  [[JavaScript]色々なディープコピー - Qiita](https://qiita.com/knhr__/items/d7de463bf9013d5d3dc0)
2
2
 
3
3
  を参考に、ディープコピーメソッドを作った上で、
4
+ (上記を発展させ、クラスメソッド(prototype定義のメソッドも)含めてコピーできるようにしている)
4
5
  下記でいかがでしょうか。
5
6
 
6
7
  ```javascript
@@ -30,13 +31,15 @@
30
31
 
31
32
  // ディープコピー関数を定義
32
33
  const deepClone = obj => {
33
- let r = {}
34
+ const r = (() => {
35
+ // 一応、余計な処理をいれないために、クラス名がObjectか、Arrayだったらそのまま、配列かオブジェクトかを返す
36
+ if (obj.constructor.name === 'Array' || obj.constructor.name === 'Object') return Array.isArray(obj) : [] : {};
37
+ // クラス名が上記以外なら、(クラス、prototypeがユーザー定義)Object.createをうまく使って、新しくインスタンスを生成して返す
38
+ return Object.create(obj.constructor.prototype);
39
+ })();
34
- for(var name in obj){
40
+ for(let key in obj){
41
+ // プロパティがオブジェクトなら、再起的に自身を実行して潜っていく、値なら、そのまま代入
35
- if(typeof obj[name] === 'object'){
42
+ r[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
36
- r[name] = deepClone(obj[name])
37
- } else {
38
- r[name] = obj[name]
39
- }
40
43
  }
41
44
  return r;
42
45
  };
@@ -99,9 +102,10 @@
99
102
  ```
100
103
 
101
104
  maisumakunさんがおっしゃるとおり、Object.createは、ディープコピーを生成するものではないので、改めて、ディープコピーをさせるメソッドを用意する必要があるのでしょう。
105
+ (Object.createは、第一引数のオブジェクトを、"prototype"として、新たにオブジェクトを生成するので、コピーメソッドの中でうまく使えば、完全に同クラスからの別参照インスタンスオブジェクトとなる)
102
106
 
103
107
  さて、余談になりますが、**JavaScriptには、値渡しも参照渡しも存在しません**。
104
- あくまで、変数は、**メモリに格納された値に対する参照**にすぎません。
108
+ あくまで、変数は、**メモリに格納された値、またはオブジェクトに対する参照**にすぎません。
105
109
  ```javascript
106
110
  const a = {value: 15};
107
111
  const b = a;
@@ -113,14 +117,39 @@
113
117
  `a`を代入された`b`は、`{value: 15}`というオブジェクトに対する参照となり、
114
118
  その時点で、`a`と`b`の関係は代入した時点で断たれています。
115
119
  ただし、**メモリに格納された同じオブジェクト自体を参照**しているので、`b`によって、プロパティの値を変えると、同じオブジェクトを参照している`a`の方も変化があったように見えます。
120
+ (各プロパティもまた、値に対する参照のため。オブジェクトは、参照をプロパティとして複数保持できるもの)
116
121
  よって、下記の場合は、`a`の変化を`b`でさせることはできなくなります。
117
122
 
118
123
  ```javascript
119
124
  const a = {value: 15};
120
- const b = a;
125
+ let b = a;
126
+ // bは、新たに生成されたオブジェクトの参照となるため、aとは参照元さえも変わってしまう
121
127
  b = {value: 15};
122
128
  b.value = 20;
123
129
  console.log(a.value); // 15
124
130
  ```
125
131
 
132
+ オブジェクトと同様に、値に対しても同じ参照の挙動となります。
133
+
134
+ ```javascript
135
+ const a = 15;
136
+ let b = a; // bは、同じ参照元の15という値の参照となる
137
+ b = 20; // bは、新たな値20の参照となる
138
+
139
+ console.log(a); // 15
140
+ ```
141
+
142
+ ```javascript
143
+ const a = 15;
144
+ let b = a; // bは、同じ参照元の15という値の参照となる
145
+ // だが、オブジェクトと違い、裸の値であるため、どう代入し直しても、新しい値が生成される。(上記の通り新しいオブジェクトの代入は新たにメモリに格納されるので、裸の値と参照の挙動自体は同じ)
146
+ b = 15; // bは、値的には同じでも、参照元が変わってしまう。
147
+
148
+ console.log(a); // 15
149
+ ```
150
+
151
+ つまり、変数に対して、値を渡している、参照を渡しているのではなく、
152
+ 常に変数は、**値(オブジェクト)に対しての参照そのもの**となります。
153
+ 値が入っている、参照が入っている、それを渡している、ではないというわけです。
154
+
126
155
  以上です。