前記事の続きです
まずはディープコピーとシャローコピーについて
よくあるような図
a = [1,true,"X"];の後で
左側の同じものを参照してるのがシャローコピー、
右側の同じものを複製してaとbが別のものを指しているのがディープコピーです
a.concat()は、a.slice()とか新しい配列返してくれるメソッドで元に変化加えなければなんでもいいです
a[0] = 10;を実行すると
左側はb[0]も10、右側はb[0]はそのままの1です
C言語のポインタだと、同じ場所を指すんじゃなく片方がもう片方を指してるからポインタとはちょっと違う
例:
int *b2 = b;
でb2にbの指してる先を入れてるのでこれに近いかな
int *b = &a;
だとbがaを指します
上の図からわかるようにaを消すとbとb2が使えなくなります
JavaScriptのだとaを消してもcのbを消すようなもので他は使えます
というのがディープコピーとかシャローコピーとかいうものなのですが
この場合はなに?
concat()などだと、こうなります
var c = a.concat();
これだとc[0],c[1]は変えても大丈夫ですが、c[2][0]を変えるとa[2][0]にも影響します
ディープコピーというのはこっちなのか3,4も複製してるのかよくわかりませんが
私が求めてるのはオブジェクトの中のオブジェクトも全て複製してくれるほうです
一応JavaScriptのディープコピーを調べていくつか実行しましたが完全に複製してくれるのありませんでした…
ということはディープコピーはオブジェクト内オブジェクトまではコピーしないのか…
といっても1,2ですらコピーできない例を上げてるところも多かったので信用していいのかどうか…
あとプロトタイプチェーンが考えられてなかったのも多かったです
新しく関数つくってそのプロトタイプにオブジェクト設定してnewする とかなんかすごそうと思うものも見つけましたが、1,2ですらコピーされてませんでした
使えないー
とりあえず自分で作ってみたもの
これまでの例では書きやすい配列ですが、実際は配列も含むオブジェクト全体でやりたいのでObjectのプロトタイプに追加してます
問題あるのは配列のとき
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でオブジェクトとして変数に入れたものがある場合は完全に複製できません
String型はコピー出来てるように見えますが[[PrimitiveValue]]がないのでメソッドを使ったり他の値と計算させようとするとエラーが出ます
ところでtypeof演算子、nullに使うとobjectが返って来ます
nullはプロパティアクセスするだけでエラーなのになぜobject…?
ココでもif文がー
誰か簡単に出来る方法を…
おまけ:
PHPだと…
黒字がJavaScriptの、青字がPHPのコードです
PHPの場合は&で代入すると参照になります
その場合、JavaScriptなどと同じように同じものを指しているのですがbがaのエイリアス(別名)のような動きをします
b自体に代入するとJavaScriptではbが指すものが変わります
PHPだとaとbは同じもの扱いなのでaも変わります
言語ごとに違うことを多すぎて辛いです
===
追記。
ある程度のクローンはできるようになりました
Arrayとかはinstanceofで確認できるのでArrayクラスのインスタンスにオブジェクトのプロパティ全コピーしていますが、ユーザが作るのは想定することはできないので諦めるしか無いです
a instanceof Array という風に自分でクラス名指定してtrue,false返すものじゃなくて、which instance みたいなのでクラスとなるオブジェクトを返してくれるのがあれば楽なのですけど
それと、属性です
writable, enumerable, configurableやgetter,setterが当てはまります
これらの扱いは特殊なのでObject.definePropertyなどを使って指定しないとダメです
最後に、データ構造にループがある場合です
クローンを作っていく過程を全部保存しておいて同じデータが来たら停止して、クローンじゃなくて参照を代入するようにする必要があります
この程度のならまだなんとかできそうですが、2次元配列で、それぞれのデータがオブジェクトを持っていて、配列内の他の要素を指してるのがいっぱいあると嫌になってきます
言語の機能でディープコピーがサポートされてない言語ではディープコピーやらないほうがいいような気がしてきました
よくあるような図
a = [1,true,"X"];の後で
左側の同じものを参照してるのがシャローコピー、
右側の同じものを複製してaとbが別のものを指しているのがディープコピーです
a.concat()は、a.slice()とか新しい配列返してくれるメソッドで元に変化加えなければなんでもいいです
a[0] = 10;を実行すると
左側はb[0]も10、右側はb[0]はそのままの1です
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も(略
*/
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);
concat()などだと、こうなります
var c = a.concat();
これだと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);
String型はコピー出来てるように見えますが[[PrimitiveValue]]がないのでメソッドを使ったり他の値と計算させようとするとエラーが出ます
プリミティブ型にできるのは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だと…
黒字が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.x.a = a;
a.y.z = a.z;
a.z.y = a.y;
クローンを作っていく過程を全部保存しておいて同じデータが来たら停止して、クローンじゃなくて参照を代入するようにする必要があります
この程度のならまだなんとかできそうですが、2次元配列で、それぞれのデータがオブジェクトを持っていて、配列内の他の要素を指してるのがいっぱいあると嫌になってきます
言語の機能でディープコピーがサポートされてない言語ではディープコピーやらないほうがいいような気がしてきました
コメント一覧 (4)
JSON.parse(JSON.stringify(object))
するのが手っ取り早くてよいと思います。
シンプルなものならよさそうですね。
ただ、関数や参照のループ(var a = {};a.self = a;みたいなの)があるとちゃんと動かないのが困りどころですね
var newObj = Object.create( oldObj );
が使えると思いますがどうでしょう。
もしくは
function objProto () { ; }
objProto.prototype = oldObj;
var newObj = new objProto;
など。
ですがこれだと、newObjの__proto__にoldObjがあるのでコピーになってないですね。
私がやりたいことは元のオブジェクト側への参照をなくして、コピー元とコピー先のどの値を変えても互いに影響をうけないようにしたいです。
この方法だとコピー元を変えるとコピー先が変わってしまいます。
var a = {x:1,y:2,z:["a","b"]}
var b = Object.create(a)
/* bじゃなくてb.__proto__がa */
b
//Object {x: 1, y: 2, z: Array[2]}
b.__proto__
//Object {x: 1, y: 2, z: Array[2]}
/* コピー先の変更は問題なし */
b.x = 20
//20
b
//Object {x: 20, y: 2, z: Array[2]}
a
//Object {x: 1, y: 2, z: Array[2]}
/* 元を変えると両方変わる */
a.y = 30
//30
b
//Object {x: 20, y: 30, z: Array[2]}
a
//Object {x: 1, y: 30, z: Array[2]}
/* 配列とかの参照先を変えると両方変わる */
b.z
//["a", "b"]
a.z
//["a", "b"]
b.z[0] = "c"
//"c"
b.z
//["c", "b"]
a.z
//["c", "b"]
コメントする