ここで、UIウィジェットの話から少し外れて、Dojoが持つオブジェクト指向サポート機能をご紹介したい。同機能を利用すると、JavaScript上でクラスベース (の感覚に近い) のオブジェクト指向を実現でき、継承なども利用できる。

なぜここでいきなりそんな話をするのかと言うと、この話題を理解していないと、次に解説するUIウィジェットの自作を行うことができないからだ。もちろんオブジェクト指向は、UIウィジェットに限らず広く応用が利く技術なので、通常のJavaScriptプログラミングでもどんどん利用していただきたい。

dojo.declare()を使用したクラスの作成

クラスの作成には、dojo.declare()メソッドを使用する。

dojo.declare(className, mixins, classBody)
  - className - クラス名を文字列で指定する。パッケージ名付きも可
  - mixins - 「親クラス」を指定する。親クラスが複数存在する場合は配列で指定。親クラスがない場合は`null`を指定する
  - classBody - クラスのメンバを記述する。ここで指定したメンバは、クラスのプロトタイプにコピーされる

ためしに、クラス「Human」を親クラスなしで作成してみる。Humanは、属性「name」とメソッド「greeting」を持つ。

    // Humanクラスの宣言
    dojo.declare("Human", null, {
      name: "",

      // 自分の名前とともにあいさつ文を表示するメソッド
      greeting: function() {
        // クラス内の属性やメソッドを参照するためには
        // 「this」キーワードが必要
        alert("Hello. My name is " + this.name);
      }
    });

こうして作成したHumanクラスは、new演算子を使用してインスタンスを作成し、属性のセットやメソッドの呼び出しを行うことが可能だ。

    dojo.addOnLoad(init);

    // Humanクラスを使用してみる
    function init() {
      // new演算子を用いてインスタンス生成
      var shiraishi = new Human();
      // 属性をセットし、メソッド呼び出し
      shiraishi.name = "白石";
      shiraishi.greeting();
    }

コンストラクタ

dojo.declare()を用いて作成したクラスには、コンストラクタを含めることも可能だ。コンストラクタの宣言に必要なのは「constructor」という名前のメソッドを記述するだけだ。

先ほどの「Human」クラスを、コンストラクタを用いて名前を指定できるように改良してみよう。

    dojo.declare("Human", null, {
      constructor: function(name) {
        this.name = name;
      },
      greeting: function() {
        alert("Hello. My name is " + this.name);
      }
    });

使う側では、newに続けてコンストラクタを呼び出す際、引数として名前を指定する。

    var shiraishi = new Human("白石");
    shiraishi.greeting();

属性についての注意

dojo.declare()の第3引数でクラスの本体を記述することができるのはおわかりいただけたかと思う。ここで、クラスの属性を宣言する際に注意すべきことがある。

それは、「配列やオブジェクトなど、プリミティブでない型をクラスメンバとして宣言してしまうと、自動的に"static"として扱われる」と言うものだ。

たとえば、Humanクラスに複数の電話番号を格納できるよう、属性「telNums」を追加したいとする。その際、クラス本体に「telNums」を記述してしまうと、telNumsは複数のインスタンス間で共有されてしまうため、期待と異なる結果となる。

    dojo.declare("Human", null, {
      // 電話番号を格納する配列
      telNums: [],
      constructor: function(name) {
        this.name = name;
      },
      // 電話番号も表示するように修正
      greeting: function() {
        alert("こんにちは。私は" + this.name + "です。" +
              "電話は[" + this.telNums + "]です。");
      }
    });

    // shiraishiには"1111111111"をセット
    var shiraishi = new Human("白石");
    shiraishi.telNums.push("1111111111");

    // tanakaには"2222222222"をセット
    var tanaka = new Human("田中");
    tanaka.telNums.push("2222222222");

    // 期待値→"2222222222"
    // 実際の値→"1111111111", "2222222222"
    tanaka.greeting();

これを避けるためには、コンストラクタを使用して属性の初期化を行うようにすればよい。

    dojo.declare("Human", null, {
      constructor: function(name) {
        this.name = name;

        // 属性の初期化をコンストラクタ内で行う
        this.telNums = [];
      },
      greeting: function() {
        alert("こんにちは。私は" + this.name + "です。" +
              "電話は[" + this.telNums + "]です。");
      }
    });