classjs (version 1.2.0 rev4) | 2016-03-04 12:41 |
classjs のWikiページへようこそ。
Class.jsは、JavaScriptのオブジェクト指向を強化するモジュールです。
プロトタイプ・ベースの継承、インターフェース、仮想メンバー、抽象メンバー、クラスメンバー、プライベートメンバー、安全なスーパークラスへのアクセス、内部クラスなど、オブジェクト指向言語がサポートする機能の重要なもののいくつかをサポートしています。
Javascriptには、十分に機能する「オブジェクト指向」が実装されていません。
現在のJavaScriptのオブジェクト指向的な機能は、既存の仕様の上に、後から追加する形で導入されているため、「とって付けた」感が否めない不完全なものにとどめられています。
とくに、クラスという概念がなく、これを関数の拡張とプロトタイプの実装で代用したり、データのアクセス制御(public, protected, private)や、仮想メンバー、抽象メンバーがサポートのサポートがないことで、本来のオブジェクト指向の果実は、その大半が欠落しているといわざるをえません(ここで言う『メンバー』とは、オブジェクトのメソッドとプロパティの総称です)。
また、スーパークラスへのアクセスにも、単に他クラスのプロトタイプを参照する(実際のスーパークラスかどうかは関係なく)だけにとどまっているため、『継承』の魅力は損なわれ、新たなバグの混入のリスクと背中合わせの状態に陥っています。
さらに、Javaにおける多重継承の別解ともいえるインターフェースもないので、オブジェクトの多態性を利用した安全なコーディングも保証されません。
この他、抽象クラスによる、メソッドやプロパティの未定義の検出、仮想メンバーによるオーバーライドの制御、クラスメンバー定義といった、一般のオブジェクト指向では当たり前に利用されているものすら存在しません(コードを駆使して似たようなことはできますが、そのコード自体が可読性を著しく崩壊させるという犠牲との取引になってしまいます)。
そもそも、上記に挙げた(一部でありますが)オブジェクト指向言語の標準機能は、とくに、大規模なアプリケーションを構築する上では、余分な条件分岐のコードを減らし、未定義値へのアクセスや未定義メソッドの呼び出しという単純ミスを回避するなど、きわめて効果的なものばかりです。
「Class.js」は、これらの機能を盛り込むと同時に、コードの表現そのものもシンプルにし、パフォーマンスの向上と、コーディング・デバッグ作業の単純化を目指した開発環境を提供するものです。
なお、Class.jsはNode.jsのモジュールにも利用できます。
var ClassA = Class.cast({ ................................... propertyA: ....., ................................... }); // インスタンスの作成 var a = new ClassA();
アプリケーション全体で1つのクラス・インスタンスを持つようにするには、Class.singleton()を利用します。 Class.cast()と同じく、オブジェクト・インスタンスを引数としますが、得られるのはクラスではなくインスタンスです。
var instanceA = Class.singleton({ ................................... propertyA: ....., ................................... }); // instanceAは、無名クラスの唯一のインスタンスとなります。
var ClassB = Class.cast({ ................................... Extends: ClassA, ................................... });
従来のJavascriptのクラスの継承は、プロトタイプ(prototypeプロパティ)へ、スーパークラスのインスタンスをコピーすることで行われます。その決定的な欠陥は、そのままでは、メソッドだけでなく、スーパークラスのプロパティまでがクラス全体で共有されてしまう点です。
// ClassXの定義 function ClassX(...) { .............................. this.data = new Array(); .............................. .............................. }; // ClassYの定義 function ClassY(...) { .............................. .............................. .............................. } // ClassXからの継承 ClassY.prototype = new ClassX(...); // インスタンスを継承するので、メソッドだけでなく、プロパティの実体も含めてプロトタイプに取り込んでしまう。 var y0 = new ClassY(...); var y1 = new ClassY(...); // y0.dataとy1.dataは、同じ配列を指してしまう。
上記の例では、インスタンスy0とy1は、共通の配列(data)をプロパティとして持ってしまいます。
そもそも、インスタンスごとに同じ内容のメソッドを重複してもつことで、無駄なメモリを消費しないように考えらたはずのプロトタイプの機構が、オブジェクトの状態を表すプロパティ(データ)をも取り込んで共有させてしまうという致命的な欠陥を潜在させてしまったのです。
これを避けて、それぞれのインスタンスが独自のプロパティをもてるようにするには、コンストラクタ内で、スーパークラス(ClassX)のコンストラクタを呼び出さなければなりません。
これにより、アクセス時に、プロトタイプ内に存在する共有のdataプロパティよりも先に、それぞれのインスタンス内のdataプロパティが検索されるようになります。
}}}
こうした回避方法が適切でないさらなる理由は、うっかり別のクラスのコンストラクターを呼び出しても、エラー扱いにはならないということです。
Class.jsは、このようなpインスタンスを基本とした継承機構ではなく、あくまでprototypeからの継承を行うことで、コードを見ただけでは実体が把握しづらかった概念をストーレートに反映できるようにしました。
具体的には、Extends指定によってスーパークラスは明示的に関連付けられるとともに、スーパークラスのコンストラクタを呼び出さなくても、プロパティは直接、インスタンスに継承されるようになります。
また、スーパークラスのコンストラクタを呼び出す際も、クラス名を改めて指定してミスを誘発するリスクを排除し、後述する「superClass()」メソッドの利用で、確実に呼び出せるようになります。--> superClass()
var InterfaceA = { funcA: null // メソッドは空でよい }; var InterfaceB = { funcB: null };
var ClassA = Class.cast({ ................................... ................................... Implements: [InterfaceA, InterfaceB], ................................... ................................... funcA: function(...) { // 未定義だとエラーとなります .............................. .............................. }, ................................... funcB: function(...) { // 未定義だとエラーとなります .............................. .............................. }, ................................... ................................... ................................... });
var a = new ClassA(.....); if ( Class.interfaceOf(a, InterfaceA) ) { ........................ ........................ ........................ } else { throw new Error("Instance must implement "InterfaceA" interface."); }
var ClassA = Class.cast({ ................................... ................................... Const: { CLASS_VALUE: XXXXXXXXX, ,...................... MethodX: function(...) { ,...................... ,...................... }, ,...................... }, ................................... });
var ClassA = Class.cast({ ................................... ................................... Static: { CLASS_DATA1: *********, ,...................... CLASS_METHOD1: function(...) { .......................... .......................... }, ............................... }, ................................... ................................... var x = ClassA.CLASS_DATA1; ................................... ClassA.CLASS_METHOD1(...); ................................... ................................... });
var ClassB = Class.cast({ ................................... Extends: ClassA, ................................... newInstance: function(...) { ............................... this.superClass(...); // スーパー・クラス"ClassA"のコンストラクタ呼び出し ............................... }, ................................... });
O'RELLYのJavasScritでは、3階層以上の継承があると、上位クラスへの呼び出しが直接の親以上に遡らず、永久ループになる例が挙げられています。 これはA=>B=>Cという継承が行われた場合、クラスCのインスタンスからスーパークラスBのスーパー・コンストラクタを呼び出せても、あくまで実体がクラスCのインスタンスである以上、クラスBのコンストラクタからクラスAにたどり着くことはできず、繰り返しクラスBのコンストラクタを呼び出し続けることになるからです。当然、クラスBのインスタンスであれば、クラスAにはたどり着けます。
Class.jsでは、この問題は解決されており、継承が何重に行われても、確実に連鎖的にスーパークラスを遡るようになっています。
var ClassA = Class.cast({ ................................... Private: { privateA: "....", privateX: function(...) { ........................... ........................... }, ............................... }, ................................... });
var a = new ClassA(...); ....................................... a.setPrivateA("....."); // セッターの実行 ....................................... var x = a.getPrivateA(); // ゲッターの実行 .......................................
var a = new ClassA(...); ....................................... ....................................... a.privateX(...); // privateXは、ClassAのプロトタイプに存在しないのでエラーになります。
var ClassA = cast({ ................................... Private: { ............................... privateX: function(...) { ........................... ........................... }, ............................... publicX: function(...) { ........................... this.privateX(...); // エラーになります。 ........................... } ............................... ............................... }, ................................... Loader: function() { var privateX = Class.find(this, "privateX"); // プライベート・メソッドを取得 Class.attach(this, "usePrivate", function() { // プライベート・メソッドを使用するパブリック・メソッドを定義 ...................................... privateX.call(this, ............); // プライベート・メソッドの呼び出し ...................................... }); } }); var a = new ClassA(...); a.usePrivate(); // パブリック・メソッドの中でプライベート・メソッドが使用されます。 a.privateX(); // エラーになります。
var ClassA = Class.cast({ ................................... Virtual: { .............................. methodA: function(...) { .................... }, .............................. .............................. }, ................................... methodB: function(...) { ............................... }, ................................... });
var ClassB = Class.cast({ Extends: ClassA, // ClassAを継承 ................................... methodA: function(...) { // OK 仮想メソッドなのでオーバーライド可能 ............................... this.superMethod(this.methodA, ...); // 基本クラスのメソッド呼び出し。 ............................... }, ................................... ................................... methodB: function(...) { // ERROR 基本クラスとの名前の衝突の例外が発生する。 ............................... }, ................................... });
var ClassA = Class.cast({ ................................... Abstract: { .............................. methodA: null, // 実体は定義しない .............................. .............................. }, .................................. });
var ClassB = Class.cast({ Extends: ClassA, // 抽象クラスClassAを継承 ................................... methodA: function(...) { // 未定義だとクラス構築時に例外が発生 ............................... ............................... }, ................................... });
たとえば、「図形」を表現するクラスでは「描画」メソッドは抽象メソッドにし、実際の描画処理は、サブクラスの「三角」、「円弧」、「四角」で定義する(ことが義務付けられる)というように使用します。
var ClassA = Class.cast({ ................................... Static: { .............................. propertyA: null, .............................. setPropertyA: function(...) { this.propertyA = ...; }, .............................. }, ................................... }); ....................................... ....................................... var ClassB = Class.cast({ ................................... Static: { ................................... propertyB: null, ................................... setPropertyB: function(...) { this.propertyB = ...; }, ................................... }, ................................... ................................... Loader: function() { ............................... var x = ....................... this.setPropertyB(x); // ClassBはこの時点で未定義なのでthisでアクセス ............................... var y = ....................... ClassA.setPropertyA(y); // ClassAは構築済みなので、アクセス可能 ............................... }, ................................... });
var ObjectX = { ................................... property0: ....., ................................... method0: function(...) { ............................... ............................... }, ................................... }; ....................................... var ObjectY = { ................................... property1: ....., ................................... method0: function(...) { // ObjectXのmethod0は、取り込まれない ............................... ............................... }, ................................... method1: function(...) { ............................... ............................... }, ................................... }; ....................................... var ClassA = Class.cast({ ................................... Imports: [ObjectX, ObjectY], ................................... method1: function(...) { // ObjectYのmethod1は、取り込まれない ............................... ............................... }, ................................... });
var ClassA = Class.cast({ ................................... Static: { ............................... InnerClassA: Class.cast({ // 公開内部クラス ........................... newInstance: function(...) { ....................... ....................... }, ........................... innerMethodA: function(...) { ....................... ....................... }, ........................... }); ............................... getInnerAInstance: function(...) { // クラス・メソッドを通して生成 return new this.InnerClassA(...); }, ............................... }, ................................... }); ....................................... // インスタンスの生成 var ia0 = new ClassA.InnerClassA(....); var ia1 = ClassA.getInnerAInstance(....); // メソッド呼び出し ia0.innerMethodA(...); ia1.innerMethodA(...);
var ClassB = Class.cast({ ................................... InnerClassB: Class.cast({ // 内部クラス ............................... newInstance: function(...) { ........................... ........................... }, ............................... innerMethodB: function(...) { ........................... ........................... }, ............................... }), ................................... methodB: function(...) { ,................................. var ib = new this.InnerClassB(...); // インスタンスからのみ生成可能 ,................................. ib.innerMethodB(); ,................................. }, ................................... getInnerBInstance: function(...) { // インスタンスメソッドを通して生成 return new this.InnerClassB(...); }, ................................... }); ....................................... // インスタンスの生成 var b = new ClassB(...); var ib0 = b.getInnerBInstance(...); var ib1 = new b.innerMethodB(...); // メソッド呼び出し ib0.innerMethodB(...); ib1.innerMethodB(...);
一方、superMethood()は、オーバーライドされたスーパークラスの仮想メソッドを実行します。--> Virtual
var ClassA = Class.cast({ ................................... // コンストラクタ newInstance: function(...) { ............................... ............................... }, ................................... Virtual: { ............................... methodX: function(...) { ........................... ........................... }, ............................... }, ................................... }); .......................................
var ClassB = Class.cast({ Extends: ClassA, ................................... // コンストラクタ newInstance: function(...) { ............................... // ClassAのコンストラクタを呼び出す // thisがサブ・クラスのインスタンスでもClassAへの遡及は保証される。 this.superClass(...); ............................... }, ................................... methodX: function(...) { // ClassAのmethodXがVirtualなのでオーバーライド可能 ............................... // ClassAのインスタンス・メソッドを呼び出す(第1引数は、自身のメソッド) this.superMethod(this.MethodX, ...); ............................... }, ................................... }); .......................................
var ClassC = Class.cast({ Extends: ClassB, ................................... // コンストラクタ newInstance: function(...) { ............................... // ClassBのコンストラクタを呼び出す this.superClass(...); ............................... }, ................................... methodX: function(...) { // ClassAの系譜にあるのでオーバーライド可能 ............................... // ClassBのインスタンス・メソッドを呼び出す。 // 連鎖的に、ClassBのmethodX()も実行される。 this.superMethod(this.methodX, ...); ............................... }, ................................... }); .......................................
BeanX = Class.bean({ valueA: ....., valueB: ..... ................................... methodA: function(...) { ............................... this.valueA = ....; // ERROR valueAは隠蔽されるので直接アクセス禁止 ............................... this.setValueA(....); // OK valueAへはアクセサ経由でアクセス ............................... }, ................................... }); .......................................
var x = new BeanX(); x.setValueA(...); // 値はアクセサ経由 x.setValueB(...); // 値はアクセサ経由 ....................................... ....................................... x.methodA(...); // メソッドは呼び出し可能 ....................................... ....................................... console.log("value A is --> " + x.getValueA()); console.log("value B is --> " + x.getValueB()); .......................................
オブジェクトのプロパティを、リード・オンリーにします。
Class.fix(object, "property");
オブジェクトにゲッタを設定します。値自体をオブジェクトから隠蔽し、参照のみを行いたい場合に利用します。ゲッターは変更不可です。
Classs.shield(object, "property", value); ........................................ ........................................ var refval = object.property; // valueを取得 console.log(object); // コンソールからvalueは参照できない object.property = .............; // 変更は反映されない
オブジェクトにメソッドを追加します。
Class.attach(object, function(...) {.........................});
クラスから名前でメソッドを検索します。
var meth = Class.find(ClassX, "function_name");
Node.jsで使用する際は、以下のようにrequire()によって取り込みます。
......................................................... ......................................................... var Class = require("[パス]/Class-XX.XX.XX-min.js").main; ......................................................... ......................................................... var MyClass = Class.cast({ ..................................................... ..................................................... ..................................................... }); ..................................................... ..................................................... var my_obj = new MyClass(...); ..................................................... .....................................................
[PageInfo]
LastUpdate: 2016-03-04 13:04:44, ModifiedBy: bansoureicha
[Permissions]
view:all, edit:login users, delete/config:members