「prototype」タグアーカイブ

【JavaScript】prototypeのプロパティについて

今日、コードを書いていたら2点重要な事に気付いたので忘れないよう記しておく。

まず1点目、JavaScriptではすべての関数オブジェクトにprototypeプロパティというオブジェクトを持っている。インスタンス生成時、このprototypeのプロパティ(メンバ)が、インスタンスで使えるようになる。そして、このprototypeはインスタンス間で共有される。例えば以下のコード

var Class = function() {};
Class.prototype.prop = 'prototypeのプロパティです';
Class.prototype.obj = {
	prop : 'prototypeのプロパティ(obj)のプロパティです'
};

var a = new Class();
var b = new Class();

a.prop = '代入しますた';
console.log(a.prop);    //代入しますた
console.log(b.prop);    //prototypeのプロパティです

a.obj.prop = '代入しますた';
console.log(a.obj.prop);    //代入しますた
console.log(b.obj.prop);    //代入しますた ←!!

a.obj == b.obj;    //true

この場合、インスタンスであるaもbもpropプロパティとobjプロパティを持っている。(正しくはprototypeのプロパティを参照している。)

この時、

a.prop = '代入しますた';

を実行するとprototypeのpropプロパティに代入されるのではなく、インスタンスaにpropプロパティが新たに作られ、prototypeのpropプロパティはマスキングされる。つまり、prototypeのpropプロパティは変わらないので、

b.prop

の中身は’prototypeのプロパティです’である。

しかし、

a.obj.prop = '代入しますた';

↑のようにprototypeのプロパティがオブジェクトでそのオブジェクトのプロパティの値を変えるような場合、他のインスタンスも影響を受ける。これは、先のpropに代入するような場合のようにインスタンスのプロパティに新たなオブジェクトが作成されたわけではなく、prototypeのプロパティ(obj)にアクセスして、objのプロパティを変えているからだ。prototypeのプロパティは共有されるので他のインスタンスでも変更が反映された。

つまり、prototypeのプロパティがオブジェクトだった場合、個々のインスタンスによってこのオブジェクトに違う値を入れることを想定していると、大変なことになる。

個々のインスタンスで別々のオブジェクトを使いたい場合、コンストラクタ関数(Class)内でオブジェクトを作ることによって解決する。

var Class = function() {
	this.obj = {
		prop : 'プロパティです'
	};
};

//ここで個々のobjプロパティが作成される;
var a = new Class();
var b = new Class();

a.obj == b.obj;    //false

次に2点目。JavaScriptではclosureを使えば変数を外から隠すことができカプセル化を実現できる。と思ったが、prototypeのようなインスタンス間で共有するメソッドをclosureにすると変数もインスタンス間で共有される。以下コード。

var Class = (function() {
	var Class = function() {};
	var x = 1;
	Class.prototype.increment = function() { //closure
		x++
	};
	Class.prototype.getX = function() { //closure
		return x;
	};

	return Class;
}());

var a = new Class();
var b = new Class();

a.getX();    //1
b.getX();    //1
a.increment();
a.getX();    //2
b.getX();    //2

個々のインスタンスで別々の値を持たせたい場合、カプセル化は実現できないように思う。つまり、素直にthis._xのようなプロパティを持つしかない。つまり、解決策はない。どなたかあれば教えてください。カプセル化は実現できないが、プロパティ名の最初にアンダースコアをつけることによって、外からアクセスしないでくださいということを明示するという方法がメジャーなようだ。

1点目は解決策があるので、大したことはなかったけど、2点目に気付いた時はショックだった。varとthisが混ざって、いかにもclosure使ってます!カプセル化実現してます!!closureの利点存分に引き出しています!!!的なコードに仕上がって酔いしれていたので・・・。コンストラクタ関数で定義された変数はprototypeプロパティで使えるというようなことがあればいいなーと思ったりする今日このごろ。

closureの仕様的に無理か・・・