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にすべきである。

以上をまとめると、開発の流れとしては次のようになると思う。

  1. LayeredClass.layeredをtrueにして、主にmozilla系ブラウザで動作確認。privateメソッドが外部から呼び出されていないことを確認する。
  2. LayeredClass.layeredをfalseにしてリリース

LayerdClass の実装の詳細については、また、後日。

Ajaxの誤り

原文:Backpack: Ajax Mistakes

とても参考になったので、いくつか訳してみる。

「戻る」ボタンを無効にする

「戻る」ボタンは、標準的なWebサイトのユーザーインターフェイスの重要な機能の1つである。不幸なことに、「戻る」ボタンはJavaScriptとの相性が悪い。JavaScriptのみで構成されるWebアプリケーションへと踏み切らせない主な理由の1つは、「戻る」ボタンの機能の維持である。

リンクを友達やブックマークに送ることができない

Webサイトのもう一つの重要な機能は、URLを別の人に送れば、その人も私と同じページを見ることが出来ることである。また、サイトを利用して見つけたページをブックマークすれば、後で元の状態に戻ることも出来る。JavaScript、すなわちAjaxアプリケーションでは、この利用モデルに対してたくさんの問題を発生させる。なぜならば、JavaScriptはクライアント側でページを動的に生成するので、URLにはページを復元するための情報が十分には含まれていない為である。この機能が失われるのはあまりに問題なので、まさにこの理由のために、多くのAjax Webアプリケーションでは、特別に生成されたpermalinkを持っている。

ウィジェットのクリックに対して、視覚的手がかりをすぐに与えない

もし、私がAjaxの引き金となる何かをクリックしたら、何が起きているかを示す視覚的手がかりを私に必ずください。例としては、GMailの画面上部右側のロード中ボタンがある。私がGMailでなにかをしたら、画面上部右側にある小さな赤いボックスが、ページがロード中であることを示してくれるので、Ajaxが、通常のWeb UIでの新しいページの読み込みを起こさないということを確認できる。