日記帳

日記です。

オブジェクトのクローン

オブジェクトのクローンを作成するメソッドを考えてみる.

オブジェクトのクローンっていってもクラスベースOOPLによくある shallow copy を生成するメソッドではなくてプロトタイプベース言語らしく自分自身をプロトタイプオブジェクトとして持つ新しいオブジェクトを作るだけの物です.

とりあえず考えたのが以下のような感じ.

Object.prototype.clone = function()
{
	var f = function(){};
	f.prototype = this;
	return new f();
}

使い方は以下の通り.

var parent = new Object();
var child = parent.clone();
var grandchild = child.clone();
parent.msg = "Hello";
alert(parent.msg + ", " + child.msg + ", " + grandchild.msg); // "Hello, Hello, Hello"

parent.msg = "Bye";
alert(parent.msg + ", " + child.msg + ", " + grandchild.msg); // "Bye, Bye, Bye"

child.msg = "Ok";
alert(parent.msg + ", " + child.msg + ", " + grandchild.msg); // "Bye, Ok, Ok"

grandchild.msg = "No";
alert(parent.msg + ", " + child.msg + ", " + grandchild.msg); // "Bye, Ok, No"

期待通り動きます.これが期待通りではないと感じるようなら考え方がクラスベースOOPLに毒されているのかもしれません.されてないかもしれません.知りません.

でも上の clone() メソッドは呼び出すと毎回作成するオブジェクトのコンストラクタとなる関数オブジェクトを生成しています.速度的にもメモリ的にもすごく無駄です.いや貧乏性なだけかもしれませんが…

ってことで以下のように変更してみました.

Object.prototype.clone = function()
{
	if(! this.cloneConstructor)
	{
		this.cloneConstructor = function(){}
		this.cloneConstructor.prototype = this;
	}
	return new this.cloneConstructor();
}

でもこれ残念ながら期待通り動きません.
3つめで "Bye, Ok, Ok" と出て欲しいところで "Bye, OK, Bye" と出力されてしまします.

どうやら grandchild のプロトタイプオブジェクトが child ではなく parent になってしまっているようです. child.clone() 呼び出し時に child 自体には cloneConstructor は設定されていませんが child のプロトタイプオブジェクトである parent の cloneConstructor は設定済みなので if 文が成立せずに cloneConstructor が設定されないのが原因のようです.

ってことで以下のように変更.

Object.prototype.clone = function()
{
	if(! this.cloneConstructor)
	{
		this.cloneConstructor = function()
		{
			this.cloneConstructor = null;
		}
		this.cloneConstructor.prototype = this;
	}
	return new this.cloneConstructor();
}

コンストラクタ実行時にクローン用のコンストラクタを null に設定してやれば if が成立するようになるってことです.

でも if の中は各オブジェクト毎一回しか実行されないのに毎回 if 文が走るのは無駄ですね(貧乏性).ってことで以下のように clone() 自体を書換えるように変更してみます.

Object.prototype.clone = function()
{
	if(! this.cloneConstructor)
	{
		this.cloneConstructor = function()
		{
			this.cloneConstructor = null;
			this.clone = Object.prototype.clone;
		}
		this.cloneConstructor.prototype = this;
		this.clone = function()
		{
			return new this.cloneConstructor();
		}
	}
	return new this.cloneConstructor();
}

これで2回目以降の clone() がちょっぴり速くなるはずです.

と,ここまで書いてから clone() メソッドを書き変えるなら if 文がいらないことに気が付きます…;
ってことでとりあえずの以下のようになりました.

Object.prototype.clone = function()
{
	this.cloneConstructor = function()
	{
		this.clone = Object.prototype.clone;
	}
	this.cloneConstructor.prototype = this;
	this.clone = function()
	{
		return new this.cloneConstructor();
	}
	return new this.cloneConstructor();
}