Internal Properties Emulation

ECMAScript コードで、内部プロパティもどきを有するコンストラクタの作成を模索する。

基本的な発想*1

直付けのメソッドでローカル変数参照すりゃいいじゃん。

function CF (p0) {
	var v0 = p0;     // 隠蔽したい変数
	this.get_v0 = function (){ return v0; };
	this.set_v0 = function (p0){ v = p0; };
}

問題点/改善点

基本的な発想*2

じゃぁ、 this 直下のプロパティじゃなくて、プロパティのプロパティにする。

function CF (p0) {
	var p = new Object;
	p.v0  = p0;     // 隠蔽したい変数
	this.p = p;
}
CF.prototype.getValue = function (name) { return this.p[name]; };
CF.prototype.setValue = function (name, value) { this.p[name] = value; };

問題点/改善点

理想

何とかしてローカル変数をプロトタイプの関数から参照したい。

function CF (p0) {
	var p = new Object;
	p.v0 = p0;     // 隠蔽したい変数
}
CF.prototype.getValue = function (name) { return p[name]; };
CF.prototype.setValue = function (name, value) { p[name] = value; };

現実

修正案1

p をコンストラクタとその prototype の関数で共有したいから、コンストラクタの外から参照できるようにしよう。

P = new Object;
function CF (p0) {
	var p = new Object;
	p.v0 = p0;     // 隠蔽したい変数

	this.id = '$'+parseInt(Math.pow(10,20)*Math.random());
	P[this.id] = p;
}
CF.prototype.getValue = function (name) { return P[this.id][name]; };
CF.prototype.setValue = function (name, value) { P[this.id][name] = value; };

問題点/改善点

修正案2 * 基本的な解決

全体をさらに関数内に入れて P を外部アクセス不可にしよう。コンストラクタは改めて window オブジェクトのプロパティに設定すればいい。

気休めに、 id に用いるプロパティ名を乱数で発生させ、プロパティ名のバッティングの可能性を下げる。

function initCF () {
	var P = new Object, id = '$'+parseInt(Math.pow(10,20)*Math.random()), n=0;
	function CF (p0) {
		var p = new Object;
		p.v0 = p0;     // 隠蔽したい変数

		this[id] = '$' + n + parseInt(100*Math.random()); n++;
		P[this[id]] = p;
	}
	CF.prototype.getValue = function (name) { return P[this[id]][name]; };
	CF.prototype.setValue = function (name,value) { P[this[id]][name] = value; };
	window.CF = CF;
};initCF();

問題点/改善点

修正案3

というわけで initCF を delete することを考える。ここでは initCF 自体を CF という名前で定義し、 自分自身の呼出し時に参照する関数をコンストラクタに置き換えることにした。あとは好みで表記を若干修正。

function CF () { // ----------(1)

	var P = new Object, id = '$'+ parseInt(Math.pow(10,20)*Math.random()), n=0;

	// p を取得する関数を生成:
	function getPrivate (o) { return P[ o[id] ]; };

	// コンストラクタ: 祖先コンテキストの CF、つまり(1) を置き換える
	CF = function (p0) {
		this[id] = '$' + n + parseInt(100*Math.random()); n++;
		var p = P[ this[id] ] = new Object;
		p.v0 = p0;     // 隠蔽したい変数
	}

	// プロトタイプ定義
	CF.prototype.getValue = function (name) {
		// var p = getPrivate(this); とすることで
		// コンストラクタ関数のローカル変数 p を取得できる
		return getPrivate(this)[name];
	};
	CF.prototype.setValue = function (name,value) {
		getPrivate(this)[name] = value;
	};

};CF();

実行例:

ソース

function CF () {
	var P = new Object, id = '$'+parseInt(Math.pow(10,20)*Math.random()), n=0;
	function getPrivate (o) { return P[o[id]]; };
	CF = function (p0,p1) {
		this[id] = '$' + n + parseInt(100*Math.random()); n++;
		var p = P[this[id]] = new Object;
		p.v0 = p0;
		p.v1 = p1;
	}
	CF.prototype.getValue = function (name) {
		return getPrivate(this)[name];
	};
	CF.prototype.setValue = function (name,value) {
		getPrivate(this)[name] = value;
	};
};CF();

var o = new CF('てすと1', 'てすと2');
var p = new CF('テスト1', 'テスト2');

document.writeln( 'o.getValue("v0")        = ' + o.getValue('v0') );
document.writeln( 'o.getValue("v1")        = ' + o.getValue('v1') );
document.writeln( 'p.getValue("v0")        = ' + p.getValue('v0') );
document.writeln( 'p.getValue("v1")        = ' + p.getValue('v1') );

document.writeln( );
o.setValue('v0', 'TEST1');
o.setValue('v1', 'TEST2');
p.setValue('v0', 'test1');
p.setValue('v1', 'test2');

document.writeln( 'o.getValue("v0")        = ' + o.getValue('v0') );
document.writeln( 'o.getValue("v1")        = ' + o.getValue('v1') );
document.writeln( 'p.getValue("v0")        = ' + p.getValue('v0') );
document.writeln( 'p.getValue("v1")        = ' + p.getValue('v1') );

document.writeln( );
for (i in o){ document.writln( 'o.'+i+' = ' + o[i] ); }
for (i in p){ document.writln( 'p.'+i+' = ' + p[i] ); }

実行結果

Issued: / Revised: / All rights reserved. © 2002-2017 TAKI