クラス

 クラスは、オブジェクトを作成するときの雛形となるものです。クラスに属する変数をメンバ変数、クラスに属する関数をメンバ関数、あるいはメソッドと呼び、クラスに属するプロパティはメンバプロパティと呼びます。

 クラスは基本的には以下のようにして宣言します。

class classname
{
    // メンバやメンバメソッドやメンバプロパティを記述

    function classname() // コンストラクタ
    {
    }

    function finalize()
    {
    }
}



 クラス名には識別子を指定します。

 クラス内には、そのクラスが作成されたときにメンバとなる変数やメソッド、プロパティを記述します。

 クラス内には、クラスと同名のメソッドが必ず一つあり、コンストラクタと呼ばれます。これは、 new 演算子を使用してオブジェクトを作成するとき、この関数が new 演算子の引数を伴って呼び出されます。

 クラス内には同様に、finalize という特殊なメソッドがあります。これは、オブジェクトが消滅するときに呼ばれるメソッドで、省略可能です。書かなくてもかまいません。


例:
    class test
    {
        function test()
        {
            // コンストラクタ
            // ここに、オブジェクトが作成されるときに処理する内容を書く
            variable = 0;
        }

        function finalize()
        {
            // finalize メソッド
            // ここに、オブジェクトが破棄されるときに処理する内容を書く
        }

        function method1()
        {
            // メソッド
            System.inform(variable);
        }

        var variable; // メンバ変数

        property prop // メンバプロパティ
        {
            getter() { return variable; } 
        }
    }


クラスに対して instanceof 演算子を "Class" を伴って使用した場合は真になります(上記の例で言うと、test instanceof "Class" は真)。

オブジェクトの作成

 宣言したクラスのオブジェクトを作成するには new 演算子を使います。
 new 演算子の後には、関数呼び出しと同様に、クラス名と、コンストラクタに渡す引数を指定します。

例:
    class Test
    {
        var variable1 = getValue(); // メンバ変数の初期化

        function Test(arg1, arg2) // コンストラクタ
        {
            // ここでは new 演算子で指定した引数が arg1 と arg2 にわたっている
        }
    }

    var newobject = new Test(1, 2); // 引数に 1, 2 を渡して test クラスのオブジェクトを作成


 クラスが作成されるときの処理の順番は以下の通りです。

  1. まず空のオブジェクトが作成される
  2. メソッド、プロパティが登録される
  3. メンバ変数が作成される ( このとき初期化が必要な変数は初期化される )
  4. コンストラクタが実行される

Note
 コンストラクタへの引数がない場合であっても、new 演算子の ( ) を省略することはできません。 JavaScript のように new Test と書くことはできません。new Test( ) と書く必要があります。


 特に、クラスのメソッドやプロパティ内でそのクラスのオブジェクトを作成する場合、あるいはそのクラスのスーパークラスのオブジェクトを作成する場合、以下のようにするとエラーになります。

例:
    class Test
    {
        function Test() // コンストラクタ
        {
        }

        function func()
        {
            return new Test(); // エラー
        }
    }


 なぜならば、クラスのメソッドやプロパティ内で Test と単に書くと、クラスである Test よりもそのコンストラクタの Test の方がスコープ的に近いため、コンストラクタの Test 参照されてしまうからです。この場合はこれを避けるために、以下のように明示的に global. を使用する必要があります ( クラスは global に登録されるからです )。


例:
    class Test
    {
        function Test() // コンストラクタ
        {
        }

        function func()
        {
            return new global.Test(); // これならば OK
        }
    }

オブジェクトの無効化

 TJS2 では、オブジェクトが削除される際、オブジェクトの無効化とオブジェクトの削除、という2つの段階を踏みます。
 オブジェクトが無効化されるとき、 finalize メソッドが呼ばれ、そのオブジェクトは無効であるというマークがつけられます。以降のそのオブジェクトへのアクセスはすべて失敗し、例外が発生するようになります。オブジェクトが無効化されているかどうかは isvalid 演算子で調べることができます。

 オブジェクトは invalidate 演算子で無効化することができます。


例:
    class Test
    {
        var variable;

        function Test()
        {
            // コンストラクタ
            variable = new AnotherClass();
        }

        function finalize()
        {
            // finalize メソッドは無効化された時に呼ばれる
            invalidate variable;
        }
    }

    var object = new Test(); // オブジェクトを作成

    (略)

    invalidate object; // オブジェクトを無効化


 invalidate 演算子を用いなくても、オブジェクトは必要とされなくなった時点で削除されます。このとき、無効化されていなければ、その時点で無効化されます。
 TJS2 ではいつオブジェクトが削除されるかの明確な規定が無く、削除や無効化は「いつでもおこりうる」ことになります。したがって、無効化されてないオブジェクトは削除時に無効化されるため、思わぬ時点で finalize メソッドが呼ばれてしまう可能性があります。これを避けるためにも、オブジェクトを使い終わったら invalidate 演算子で無効化することをおすすめします。


Note
 invalidate 演算子は C++ の delete 演算子に近い働きをします。
 TJS2 の delete 演算子は C++ の delete 演算子と違い、メンバやローカル変数を削除するための演算子です。オブジェクト自体を無効化したり削除したりはしませんが、メンバやローカル変数を削除したことにより結果的にオブジェクトの無効化や削除を引き起こすことはあります。

オブジェクトへのアクセス

 作成したオブジェクトのメンバ変数、メソッド、メンバプロパティには、. (メンバ選択) 演算子 または [ ] (間接メンバ選択演算子) を用いてアクセスします。

例:
    var obj=new MyLayer(window,window.prmaryLayer)
    obj.method1(); // メソッドの呼び出し  obj['method1']() でも同じ
    obj.num = 3; // メンバ変数へ代入  obj['num']=3 でも同じ
    obj.prop1++; // メンバプロパティにアクセス  obj['prop1']++ でも同じ

クロージャ

 作成したオブジェクトのメソッドやメンバプロパティは、そのメンバがどのオブジェクトのメンバであるかの情報を持った状態でオブジェクトに登録されています。
 そのため、メソッドやメンバプロパティをオブジェクト外に持ち出して、それを使用しても、元のオブジェクトに対するアクセスを行うことになります。この機能をクロージャと呼びます。また、アクセス先のオブジェクトをコンテキストと呼びます。

例:
    var obj = new FooBarClass(); // オブジェクトを作成
    obj.method(); // オブジェクトのメソッドを普通に呼び出す
    var objmethod = obj.method; // オブジェクトのメソッドへの参照を objmethod に代入
    objmethod(); // objmethod を呼び出すが、obj.method() と同じく、obj に対するアクセスとなる


 incontextof 演算子は、どのオブジェクトのメンバであるか、の情報を変更し、任意のコンテキスト上でメソッドを実行するための方法を提供します。

例:
    (objmethod incontextof obj2)(); // obj2 に対する操作になる
    (objmethod incontextof this)(); // this に対する操作になる

継承

 キーワード extends を用いると、クラスを別のクラスから継承させることができます。継承とは、継承元のクラスのメンバを引き継ぐことです。
 継承する元となるクラスのことをスーパークラス、継承したクラスのことをサブクラスと呼びます。
 以下のようにしてクラスを宣言します。

例:
    class Class1 // スーパークラス
    {
        function Class1() // Class1 コンストラクタ
        {
        }

        function finalize() // Class1 finalize
        {
        }

        function method1() // method1
        {
        }
    }


    class Class2 extends Class1
    {
        function Class2() // Class2 コンストラクタ
        {
            super.Class1(); // Class1 コンストラクタを呼ぶ
        }

        function finalize() // Class2 finalize
        {
            super.finalize();
        }
    }

    var obj = new Class2(); // Class2 オブジェクトを作成
    obj.method1(); // Class2 は Class1 の method1 を継承しているので使用することができる

 上記の例では、Class2 が Class1 を継承しています。Class2 は Class1 から派生している、とも言います。
 Class2 のコンストラクタ内では Class1 のコンストラクタを、Class2 の finalize では Class1 の finalize を呼んでいます。サブクラスが、これらのメソッド内でスーパークラスの該当する同メソッドを呼び出さなかった場合の動作は未定義 ( というか現バージョンでは呼んだかどうかチェックしていない ) ですので、必ず記述するようにしてください。

 サブクラスからスーパークラスを参照するためには、上記の例のようにキーワード super を使用することができます。このキーワードはサブクラス内でのみ使用可能で、スーパークラスを表すものです。

 継承が行われている場合の、new 演算子でのオブジェクトの初期化の順序は以下の通りです。

  1. まず空のオブジェクトが作成される
  2. メソッド、プロパティが登録される ( スーパークラスから先に、サブクラスに向かって )
  3. メンバ変数が作成される ( スーパークラスから先に、サブクラスに向かって )
  4. サブクラスのコンストラクタが呼ばれる
  5. ( サブクラスのコンストラクタ内から ) スーパークラスのコンストラクタが呼ばれる

多重継承

 extends の後に、スーパークラス名を複数記述すると、多重継承を行わせることができます。

例:
    class SubClass extends ClassA, ClassB
    {
        function SubClass() // SubClass コンストラクタ
        {
            ClassA(); // ClassA コンストラクタを呼ぶ
            ClassB(); // ClassB コンストラクタを呼ぶ
        }

        function finalize() // Class2 finalize
        {
            global.ClassA.finalize();
            global.ClassB.finalize();
        }
    }


 super キーワードが使えないため、明示的にクラスを指定しなければなりません。例のように スーパークラスのメソッドを呼ぶときに global. をわざわざつけてクラスにアクセスするのは、サブクラス内で単に ClassA と記述すると ClassA のコンストラクタのことを指すからです。クラスそのものは global に登録されるため、global. を用いてクラスにアクセスします。

 多重継承が行われている場合の、new 演算子でのオブジェクトの初期化の順序は多重継承を行っていないときのそれと同じすが、スーパークラスのメソッド、プロパティの登録の順番は、extends キーワードの後に書いた順に行われます。継承元のクラス間で同名のメソッドやプロパティがあった場合は、後に書いたほうが優先されます。隠れてしまったメソッドやプロパティには、global.ClassA.hiddenMethod() のように明示的にクラス名を書いてアクセスする事ができます。

オーバーライド

 サブクラスでスーパークラスのメソッドやメンバプロパティと同名のものを宣言すると、スーパークラスのメソッドを隠すことができます。これをオーバーライドと呼びます。
 上記の説明の中にでてきた finalize メソッドは、スーパークラスの finalize メソッドをオーバーライドしています。

例:
    class Class1 // スーパークラス
    {
        function Class1() // Class1 コンストラクタ
        {
        }

        function finalize() // Class1 finalize
        {
        }

        function method1() // method1
        {
            (略)
        }
    }


    class Class2 extends Class1
    {
        function Class2() // Class2 コンストラクタ
        {
            super.Class1(); // Class1 コンストラクタを呼ぶ
        }

        function finalize() // Class2 finalize
        {
            super.finalize();
        }

        function method1() // Class1.method1 をオーバーライド
        {
            (略)
            if(略) return super.method1();
            (略)
        }
    }

    var obj = new Class2(); // Class2 オブジェクトを作成
    obj.method1(); // Class2 の method1 が呼ばれる

 サブクラスのメソッドやプロパティでは super キーワードを用いてスーパークラスのメソッドやプロパティにアクセスすることができます。

 メンバ変数をオーバーライドすることはできません。メンバ変数は一つのオブジェクトに対して登録されるため、サブクラスとスーパークラスで同名のメンバ関数があると サブクラスの変数がスーパークラスの変数を上書きしてしまいます。