前記事の続きです

まずはディープコピーとシャローコピーについて
よくあるような図

a = [1,true,"X"];の後で
dcopy1
左側の同じものを参照してるのがシャローコピー、
右側の同じものを複製してaとbが別のものを指しているのがディープコピーです
a.concat()は、a.slice()とか新しい配列返してくれるメソッドで元に変化加えなければなんでもいいです

a[0] = 10;を実行すると
左側はb[0]も10、右側はb[0]はそのままの1です
dcopy1-f


C言語のポインタだと、同じ場所を指すんじゃなく片方がもう片方を指してるからポインタとはちょっと違う

例:
#include <stdio.h>
int main(){
int a = 10;
int *b = &a; int a2 = a;
int *b2 = b; printf("%d %d %d %d\n",a,*b,a2,*b2); a = 20;
printf("%d %d %d %d\n",a,*b,a2,*b2); a2 = 30;
printf("%d %d %d %d\n",a,*b,a2,*b2); *b = 40;
printf("%d %d %d %d\n",a,*b,a2,*b2); *b = 50;
printf("%d %d %d %d\n",a,*b,a2,*b2); return 0;
} /*
result:
10 10 10 10
20 20 10 20 <-- a2以外はポインタでaを指してるのでaと一緒
20 20 30 20 <-- a2変えても他に影響なし
40 40 30 40 <-- bの指す先変えてもa変えた時と一緒
50 50 30 50 <-- b2も(略
*/

dcopy3

int *b2 = b;
でb2にbの指してる先を入れてるのでこれに近いかな
int *b = &a;
だとbがaを指します

上の図からわかるようにaを消すとbとb2が使えなくなります
JavaScriptのだとaを消してもcのbを消すようなもので他は使えます


というのがディープコピーとかシャローコピーとかいうものなのですが
この場合はなに?
var a = [1,2,[3,4]];
var b = a;
var c = maydeepcopy(a);
dcopy2

concat()などだと、こうなります
var c = a.concat();
dcopy4

これだとc[0],c[1]は変えても大丈夫ですが、c[2][0]を変えるとa[2][0]にも影響します

ディープコピーというのはこっちなのか3,4も複製してるのかよくわかりませんが
私が求めてるのはオブジェクトの中のオブジェクトも全て複製してくれるほうです

一応JavaScriptのディープコピーを調べていくつか実行しましたが完全に複製してくれるのありませんでした…
ということはディープコピーはオブジェクト内オブジェクトまではコピーしないのか…
といっても1,2ですらコピーできない例を上げてるところも多かったので信用していいのかどうか…
あとプロトタイプチェーンが考えられてなかったのも多かったです

新しく関数つくってそのプロトタイプにオブジェクト設定してnewする とかなんかすごそうと思うものも見つけましたが、1,2ですらコピーされてませんでした
使えないー

とりあえず自分で作ってみたもの
これまでの例では書きやすい配列ですが、実際は配列も含むオブジェクト全体でやりたいのでObjectのプロトタイプに追加してます
Object.prototype.clone = function(){
var r = {};
for(var key in this){
if(this.hasOwnProperty(key)){
var val = this[key];
if(typeof val=="object") r[key] = val.clone();
else r[key] = val;
}
}
r.__proto__ = this.__proto__;
return r;
}
オブジェクト内オブジェクトのコピーやプロトタイプのチェーンなどだいたいはうまくいってますがうまくいかないところも…

問題あるのは配列のとき
Array扱いされずObjectとして扱われてます
プロトタイプは繋いでるのでArrayのメソッドは使えますが配列の中身が空として実行されます
Arrayの場合はconcatして、プロトタイプ繋いで、for inで数字以外をコピーすればいけるかも?


それともうひとつの問題があって、それはプリミティブ型とオブジェクト型の問題

JavaScriptは
 var n = 10;
とか
 var b = false;
とかするとプリミティブな型で変数に入ります
ですが、
 n.toFixed(3)
とか
 b.toString()
みたいなメソッド使えます
これは
メソッド使うときに
 new Number(n).toFixed(3)
とか
 new Boolean(f).toString()
に変換されてるからです

で、
 var a = 10;

 var a = new Number(10);
が一緒かと言われると違うわけです

newしてる方はオブジェクトです
プロパティとか追加できます
typeof しても上は"number"、下は"object"が返ってきます

コピー時の問題はnew Number()の方をオブジェクトの中身を全部コピーしても[[PrimitiveValue]]の部分はコピーできません
for inで読み取れないよう属性が設定されてる?

というわけでプリミティブ型をnewでオブジェクトとして変数に入れたものがある場合は完全に複製できません
a = {pn:100,on:new Number(100),pb:true,ob:new Boolean(true),ps:"STRING",os:new String("STRING")};
b = a.clone();
console.log(a,b);
Object {pn100onNumberpbtrueobBooleanps"STRING"}
  1. obBoolean
    1. __proto__Boolean
    2. [[PrimitiveValue]]true
  2. onNumber
    1. __proto__Number
    2. [[PrimitiveValue]]100
  3. osString
    1. 0"S"
    2. 1"T"
    3. 2"R"
    4. 3"I"
    5. 4"N"
    6. 5"G"
    7. length6
    8. __proto__String
    9. [[PrimitiveValue]]"STRING"
  4. pbtrue
  5. pn100
  6. ps"STRING"
  7. __proto__Object
Object {pn100onNumberpbtrueobBooleanps"STRING"}
  1. obBoolean
    1. __proto__Boolean
  2. onNumber
    1. __proto__Number
  3. osString
    1. 0"S"
    2. 1"T"
    3. 2"R"
    4. 3"I"
    5. 4"N"
    6. 5"G"
    7. __proto__String
  4. pbtrue
  5. pn100
  6. ps"STRING"
  7. __proto__Object

String型はコピー出来てるように見えますが[[PrimitiveValue]]がないのでメソッドを使ったり他の値と計算させようとするとエラーが出ます
b.os.substr(0)
TypeError: String.prototype.toString is not generic
b.os+"";
TypeError: String.prototype.valueOf is not generic
プリミティブ型にできるのはinstanceofで場合分けすればできそうだけどめんどいです・・・
var a = new Number(12);
if(a instanceof Number)
var x = new Number(a); // コッチ
else
var x = a;

var b = 12;
if(b instanceof Number)
var x = new Number(b);
else
var x = b; // コッチ

ところでtypeof演算子、nullに使うとobjectが返って来ます
nullはプロパティアクセスするだけでエラーなのになぜobject…?
ココでもif文がー

誰か簡単に出来る方法を…


おまけ:

PHPだと…
dcopy5
黒字がJavaScriptの、青字がPHPのコードです

PHPの場合は&で代入すると参照になります
その場合、JavaScriptなどと同じように同じものを指しているのですがbがaのエイリアス(別名)のような動きをします

b自体に代入するとJavaScriptではbが指すものが変わります
PHPだとaとbは同じもの扱いなのでaも変わります

言語ごとに違うことを多すぎて辛いです


===
追記。

ある程度のクローンはできるようになりました
function liteclone(obj){
var clone = null;
if(obj instanceof Object){
if(obj instanceof Number){
clone = new Number(obj);
}else if(obj instanceof String){
clone = new String(obj);
}else if(obj instanceof Boolean){
clone = new Boolean(obj);
}else if(obj instanceof Array){
clone = new Array();
}else if(obj instanceof Date){
clone = new Date();
}else if(obj instanceof RegExp){
clone = new RegExp();
}else if(obj instanceof Function){
try{
var funcparts = obj.toString().match(/^function .*?\(([^]*?)\)\{([^]*)\}$/);
clone = new Function(funcparts[1],funcparts[2]);
}catch(e){
clone = obj;
}
}else{
clone = new Object();
}
for(var key in obj) if(obj.hasOwnProperty(key)){
clone[key] = liteclone(obj[key]);
}
clone.__proto__ = obj.__proto__;
}else{
clone = obj;
}
return clone;
// loop reference X
}
まだ対応してないのが、ユーザが作ったクラスのインスタンス
Arrayとかはinstanceofで確認できるのでArrayクラスのインスタンスにオブジェクトのプロパティ全コピーしていますが、ユーザが作るのは想定することはできないので諦めるしか無いです

a instanceof Array という風に自分でクラス名指定してtrue,false返すものじゃなくて、which instance みたいなのでクラスとなるオブジェクトを返してくれるのがあれば楽なのですけど

それと、属性です
writable, enumerable, configurableやgetter,setterが当てはまります
これらの扱いは特殊なのでObject.definePropertyなどを使って指定しないとダメです

最後に、データ構造にループがある場合です
a = {x:{},y:{},z:{}};
a.x.a = a;
a.y.z = a.z;
a.z.y = a.y;
こういう構造のデータでa.xをクローンしようとすると、上のコードだとループしてスタックの範囲を超えたというエラーになります

クローンを作っていく過程を全部保存しておいて同じデータが来たら停止して、クローンじゃなくて参照を代入するようにする必要があります

この程度のならまだなんとかできそうですが、2次元配列で、それぞれのデータがオブジェクトを持っていて、配列内の他の要素を指してるのがいっぱいあると嫌になってきます

言語の機能でディープコピーがサポートされてない言語ではディープコピーやらないほうがいいような気がしてきました