JavaScript で private/public の実現
JavaScript は意外に強力な言語である。
http://d.hatena.ne.jp/brazil/20050829/1125321936
Private Members in JavaScript
これらの記事に触発されて、prototype.js風にprivate/public機能をJavaScriptで実現してみた。こんな風にかける。
var Hoge = LayeredClass.create();
Hoge.prototype = {
__private: {
privateMethod: function(){
alert("public:" + this.publicVariable + "; private:" + this.privateVariable);
}
},
__public:{
publicVariable: null,
initialize: function(){
this.publicVariable = 10;
this.privateVariable = 20;
},
publicMethod: function(){
this.privateMethod();
}
}
}つまり、__privateの下にprivateなメンバメソッドを記述して、__publicの下にpublicなメンバ変数とメンバメソッドを記述する。このとき、通常のJavaScriptと異なり、publicなメンバ変数を明示的に記述する必要があることに注意。また、メソッド内で定義されたメンバ変数は全てprivateとなる。なお、コンストラクタはprototype.jsと同様に__public:initializeである。
このように Hoge クラスを定めると、Hogeクラスのprivateな変数やメソッドには、Hogeオブジェクトの外部からはアクセスできなくなる。つまり、次のようになる。
var hoge = new Hoge(); hoge.publicMethod(); // -> "public:10; private:20" alert(hoge.publicVariable); // -> "10" alert(hoge.privateVariable); // -> "undefined" hoge.privateMethod(); // -> error
LayeredClassのソースはこちら。
var LayeredClass = {
layered: true,
create: function() {
var privateClass = function(){};
privateClass.prototype = undefined;
var __initilizePrototypeHandler;
var __layered = this.layered;
var __class = function() {
if(privateClass.prototype == undefined){
__initilizePrototypeHandler(this);
}
if(__layered && Object.__defineSetter__){
this.__privateInstance = new privateClass;
}
this.initialize.apply(this, arguments);
}
__initilizePrototypeHandler = function(self){
if(__layered && Object.__defineSetter__){
privateClass.prototype = {}.
extend( self.__public ).extend( self.__private );
for( property in self.__public ){
if( property == "extend" ){
continue;
}
if(typeof self.__public[property] == "function"){
__class.prototype[property] = function(__p){
return function(){
return this.__public[__p].
apply(this.__privateInstance, arguments);
}
}(property);
}else{
__class.prototype.__defineSetter__(property, function(__p){
return function(__x){
this.__privateInstance[__p] = __x;
}
}(property) );
__class.prototype.__defineGetter__(property, function(__p){
return function(){
return this.__privateInstance[__p];
}
}(property) );
}
}
}else{
privateClass.prototype = {};
__class.prototype.extend( self.__public ).extend( self.__private );
}
};
return __class;
}
}一応ライセンスはprototype.jsと同じで。なお、prototype.jsのObject.extendを使っているので、prototype.jsを利用するか、上のソースの前のほうに、
Object.extend = function(destination, source) {
for (property in source) {
destination[property] = source[property];
}
return destination;
}
Object.prototype.extend = function(object) {
return Object.extend.apply(this, [this, object]);
}を追加しておくこと。
ただし、private/publicが機能するのはmozilla系ブラウザだけである。これは、__defineSetter__ / __defineGetter__を使っているためである。とはいえ、それ以外のブラウザでもスクリプトの動作に問題はない。mozilla系以外のブラウザでは、全てのメンバがpublicとなり、privateメンバの外部からの呼び出しが可能になるだけである。
なお、LayeredClass.layeredをfalseにすれば、全てのブラウザで、private/public機能はオフになる。private/public機能をオンにしておくと、メソッド呼び出しの際にオーバーヘッドが発生するので、private/public機能が必要な場合(開発中の場合など)のみLayeredClass.layeredをtrueにすべきである。
以上をまとめると、開発の流れとしては次のようになると思う。
- LayeredClass.layeredをtrueにして、主にmozilla系ブラウザで動作確認。privateメソッドが外部から呼び出されていないことを確認する。
- LayeredClass.layeredをfalseにしてリリース
LayerdClass の実装の詳細については、また、後日。