Java 開発者にとっての Dojo の概念
クラスの宣言とコンテキストの設定
はじめに
JavaScript を使用した経験がほとんど、あるいはまったくない状態で Dojo を手にした Java プログラマーは、Dojo を利用できるようにするためのいくつかの概念に悪戦苦闘することになります。Dojo に関する主な懸念は (この記事の執筆時点では)、Dojo がまだ初期段階であり (バージョン 1.0 がようやく 2008年2月にリリースされたばかりです)、入手可能なドキュメントがまだある程度限定されてしまうという点です。この記事は、Dojo ツールキットをすぐに理解してアプリケーション開発で使えるように、Java コードから Dojo へ移行するための橋渡しをします。
Dojo ツールキットの入手方法や Dojo ツールキットを使うために必要なステートメントに関する情報は他でも豊富に提供されているため、この記事ではそうした内容は説明しません。この記事はサーブレット開発を経験してきた Web 開発者を対象としています。
JavaScript のハッシュ
Dojo を扱う際の最初の難関は、Dojo の関数を呼び出す際に使われる構文を理解すること、特に「ハッシュ」、つまり JavaScript オブジェクトの使い方を理解することです。ハッシュは、カンマで区切られた一連の属性を中括弧に囲んで表現します。簡単な例をリスト 1 に示します。この例では、6 つの属性 (ストリング、整数、ブール値、未定義の属性、別のハッシュ、関数) で構成されるハッシュを宣言しています。
リスト 1. JavaScript のハッシュの例
var myHash = { str_attr : "foo", int_attr : 7, bool_attr : true, undefined_attr : null, hash_attr : {}, func_attr : function() {} };
JavaScript は弱い型付けの言語だということを忘れないことが重要です。つまり各属性はその属性の名前に関連する値に初期化されていますが、だからといって最初の str_attr
属性を後で整数またはブール値 (あるいは任意の他の型) に設定できない、ということはありません。ハッシュの中の各属性へのアクセスや設定はドット演算子を使って行うことができます (リスト 2)。
リスト 2. ハッシュの属性へのアクセスと設定
// Accessing a hash attribute... console.log(myHash.str_attr); // Setting a hash attribute... myHash.str_attr = "bar";
myHash
の最初の 4 つの属性は自明のはずです。また、ハッシュの属性にハッシュが含まれていることは驚くにはあたりません。(これは Java クラスが基本型とオブジェクト型の両方を参照することに相当すると考えることができます)。そして、理解する必要がある最も重要な属性は、最後の属性です。
関数はオブジェクト
Java コードには java.reflection.Method
クラスがありますが、このクラスは基本的にメソッドに対するラッパーとして動作します。JavaScript では関数はオブジェクトであり、他の関数への引数として設定、参照され、渡される他のオブジェクトとまったく同じです。関数呼び出しの中で新しい関数を宣言しなければならないことがよくありますが、これは Java のメソッド呼び出しの中で匿名内部クラスを宣言する方法とほとんど同じです。
Java のメソッドと JavaScript の関数のもう 1 つの重要な違いは、JavaScript の関数がさまざまなコンテキストで実行される点です。Java プログラミングで this
キーワードを使う場合には、そのクラスが使われている、(そのクラスの) 現在のインスタンスを指します。一方 JavaScript の関数の中で this
を使う場合には、その関数が実行されているコンテキストを指します。関数は、特に指定されない限り、その関数を定義するクロージャーの中で実行されます。
非常に単純な言い方をすれば、クロージャーは中括弧 ({}
) で囲まれた任意の JavaScript コードと考えることができます。JavaScript ファイル内部で宣言される関数は this
を使うことでそのファイルの main ボディーの中で宣言される任意の変数にアクセスすることができますが、ハッシュの中で宣言される関数はそのハッシュ内で宣言される変数を参照する場合しか this
を使うことはできません (ただしその関数が動作するための別のコンテキストが (その関数に) 与えられている場合は除きます)。
クロージャーの中で実行される関数が Dojo 関数への引数として必要になることはよくあるため、そうした関数のコンテキストを設定する方法を理解できると、本来は不必要なはずのデバッグを大量に行う事態を避けることができます。
コンテキストを割り当てるために主に使われる Dojo 関数は dojo.hitch
です。皆さんが dojo.hitch
を使うことはないかもしれませんが、dojo.hitch が Dojo で最も基本的な関数の 1 つであること、また他の多くの関数が裏でこの関数を呼び出していることを理解することが重要です。
リスト 3 はコンテキストのヒッチ (hitch: 関連付け) がどのような動作で行われるかを示しています (この動作の出力を図 1 に示します)。
- ある変数がグローバル・コンテキストで定義され (
globalContextVariable
)、あるハッシュのコンテキストで別の変数が宣言されます (enclosedVariable
)。 - 関数
accessGlobalContext()
はglobalContextVariable
に適切にアクセスし、この変数の値を表示します。 - しかし
enclosedFunction()
は、この関数のローカル変数enclosedVariable
にしかアクセスすることができません (globalContextVariable
の値は「undefined」と表示されることに注意してください)。 dojo.hitch
を使ってenclosedFunction()
をグローバル・コンテキストに「ヒッチ」すると、globalContextVariable
が表示されるようになります (ただし今度はenclosedVariable
が未定義となります。これはenclosedFunction()
が実行されているコンテキストでenclosedVariable
が宣言されていないためです)。
リスト 3. クロージャーとコンテキスト
var globalContextVariable = "foo"; function accessGlobalContext() { // This will successfully output "foo"... console.log(this.globalContextVariable); }; var myHash = { enclosedVariable : "bar", enclosedFunction : function() { // Display global context variable... console.log(this.globalContextVariable); // Display enclosed context variable... console.log(this.enclosedVariable); } }; console.log("Calling accessGlobalContext()..."); accessGlobalContext(); console.log("Calling myHash.enclosedFunction()..."); myHash.enclosedFunction(); console.log("Switch the context using dojo.hitch..."); var switchContext = dojo.hitch(this, myHash.enclosedFunction); switchContext();
図 1. コンテキスト・ヒッチの動作

クラスの宣言
このヒッチがなぜ重要かという理由は、Dojo クラスを宣言したり独自のウィジェットを作成したりし始めると、すぐに明らかになります。Dojo の最大の強みの 1 つは、dojo.connect
関数と組み込みの pub/sub モデルを使ってオブジェクト同士を「結び付け」られることです。
クラスを宣言するためには次の 3 つのオブジェクトが必要です。
- そのクラス固有の名前
- 関数を継承する元となる親クラス (そして多重継承をシミュレートする「混合」クラス)
- すべての属性と関数を定義するハッシュ
宣言できるクラスとして最も単純なものをリスト 4 に、このクラスをインスタンス化する方法をリスト 5 に示します。
リスト 4. 基本的なクラス宣言
dojo.declare( "myClass", null, {} );
リスト 5. クラスのインスタンス化の基本
var myClassInstance = new myClass();
「本物の」(つまり使い道のある) Dojo クラスを宣言したい場合には、コンストラクターを理解することが重要です。Java コードでは、オーバーロードされたコンストラクターを複数宣言することによって、さまざまに異なるシグニチャーでクラスをインスタンス化することができます。Dojo のクラスでは preamble
と constructor
、そして postscript
を宣言することができますが、大部分の場合はコンストラクターを宣言すればよいだけです。
- 多重継承をシミュレートするために他のクラスと混合する場合を除き、
preamble
が必要になることは稀です (preamble
を使うと、継承されるクラスや混合されるクラスに実際に渡す前にconstructor
引数を操作することができます)。 postscript
によって Dojo ウィジェットのライフサイクル・メソッドを駆動することができますが、標準的な Dojo クラスには何もメリットがありません。
これらはどれも必ずしも宣言が必要なものではありませんが、クラスのインスタンスに何らかの値を渡すためには、少なくとも constructor
関数を宣言する必要があります。あるクラスの他の引数が constructor
引数にアクセスする場合には、宣言された属性にそれらの constructor 引数を割り当てる必要があります。リスト 6 に示すクラスは、このクラスの constructor
引数の 1 つだけをクラス属性に割り当て、それから別のメソッドの中で両方の引数を参照しようとしています。
リスト 6. constructor 引数の割り当て
dojo.declare( "myClass", null, { arg1 : "", constructor : function(arg1, arg2) { this.arg1 = arg1; }, myMethod : function() { console.log(this.arg1 + "," + this.arg2); } } ); var myClassInstance = new myClass("foo", "bar"); myClassInstance.myMethod();
図 2. constructor 引数を割り当てたことによる出力

複合属性のルール
クラスの属性の初期化は宣言時に行いますが、複合オブジェクト型 (ハッシュや配列など) によって属性が初期化される場合には、その属性は Java クラスでの public な静的変数と似たものになります。これはつまり、あるインスタンスによって属性が更新されると、その変更は他のすべてのインスタンスに反映されるということです。この問題を避けるためには複合属性をコンストラクターの中で初期化する必要がありますが、これはストリングやブール値のように単純な属性の場合には必要ありません。
リスト 7. グローバル・クラスの属性
dojo.declare( "myClass", null, { globalComplexArg : { val : "foo" }, localComplexArg : null, constructor : function() { this.localComplexArg = { val:"bar" }; } } ); // Create instances of myClass A and B... var A = new myClass(); var B = new myClass(); // Output A's attributes... console.log("A's global val: " + A.globalComplexArg.val); console.log("A's local val: " + A.localComplexArg.val); // Update both of A's attributes... A.globalComplexArg.val = "updatedFoo"; A.localComplexArg.val = "updatedBar"; // Update B's attributes... console.log("A's global val: " + B.globalComplexArg.val); console.log("A's local val: " + B.localComplexArg.val);
図 3. クラスの属性

メソッドのオーバーライド
スーパークラスのメソッドは、同じ名前を持つ属性を宣言することによって継承することができます。JavaScript にはオーバーロードという概念はありませんが、これは JavaScript が想定外の引数を無視し、足りない引数はヌルで置き換えるためです。Java コードではオーバーライドされたメソッドを呼び出すために super のメソッド (つまり super().methodName(arg1, arg1);
) を呼び出しますが、Dojo では inherited
メソッド (this.inherited(arguments);
) を使います。リスト 8 では 2 つのクラスが宣言されており、child
は parent
を継承して親の helloWorld メソッドをオーバーライドしていますが、inherited
を呼び出して parent
の関数にアクセスしています。
リスト 8. Dojo での superclass メソッドの呼び出し
dojo.declare( "parent", null, { helloWorld : function() { console.log("parent says 'hello world'"); } } ); dojo.declare( "child", parent, { helloWorld : function() { this.inherited(arguments); // Call superclass method... console.log("child says 'hello world'"); } } ); var child = new child(); child.helloWorld();
図 4. Dojo で superclass メソッドを呼び出したことによる出力

メソッド・コンテキストの設定
リスト 9 に示す Java クラスはインスタンス化されると、提供されたストリング配列の要素をストリングの ArrayList にコピーします。Dojo でも、リスト 10 のコードによって同じ機能を提供できると考えても妥当なように思えます。(targetArray
がグローバルにならないようにコンストラクター関数の中でインスタンス化されていることに注意してください。) しかし残念ながら、これは図 5 に示すようなエラー・メッセージになります。この理由は、dojo.forEach
メソッド呼び出しの中で宣言された関数は、その関数自身のボディーを参照するものとして this
を定義するクロージャーを作成するからです。
リスト 9. Java コードでのクラス・スコープの変数へのアクセス
import java.util.ArrayList; public class MyClass { // Declare an ArrayList of Strings... private ArrayList<String> targetArray = new ArrayList<String>(); public MyClass(String[] sourceArray) { // Copy each element of a String[] into the ArrayList... for (String val: sourceArray) { this.targetArray.add(val); } } }
リスト 10. Dojo でコンテキストが定義されていない場合
dojo.declare( "myClass", null, { targetArray: null, constructor: function(source) { // Initialise in constructor to avoid making global this.targetArray = []; // Copy each element from source into target... dojo.forEach(source, function(item) { this.targetArray[this.targetArray.length] = item; }); }, } ); // This will cause an error! var myClass = new myClass(["item1","item2"]);
図 5. Dojo でコンテキストが定義されていない場合の出力

targetArray
はこの関数で囲まれたコンテキストで定義されているわけではありませんが、このコンテキストを Dojo 関数の引数として、このコンテキストが定義されている場所で渡すことができます。これはつまり、this
キーワードによって、そのコンテキストで宣言された (関数を含む) 任意のオブジェクトにアクセスできるということです。これを適切に実装したものをリスト 11 に示します (追加のコードは太字で示してあります)。
リスト 11. Dojo での適切なコンテキスト設定
dojo.declare( "myClass", null, { targetArray: null, constructor: function(source) { // Initialise in constructor to avoid making global this.targetArray = []; // Copy each element from source into target... dojo.forEach(source, function(item) { this.targetArray[this.targetArray.length] = item; }, this); }, } );
コンテキストは必ずしも Dojo 関数のシグニチャーの中で同じ引数として渡されるわけではありません。
dojo.subscribe
では、コンテキストは関数宣言の>前に渡されます (リスト 12)。dojo.connect
では、トリガー・メソッドが定義されるコンテキストとターゲット・メソッドが定義されるコンテキストの両方を提供する必要があります。リスト 13 に示す例では、obj1
はmethodA
が定義されるコンテキストであり、obj2
はmethodB
が定義されるコンテキストです。obj1
のmethodA
を呼び出すと、obj2
のmethodB
が呼び出されます。
リスト 12. dojo.subscribe でのコンテキスト設定
dojo.declare( "myClass", null, { subscribe : function() { dojo.subscribe("publication", this, function(pub) { this.handlePublication(pub); }); }, handlePublication : function(pub) { console.log("Received: " + pub); } } );
リスト 13. dojo.connect でのコンテキスト設定
dojo.connect(obj1, "methodA", obj2, "methodB");
まとめ
JavaScript よりも構造化された Java コードの環境に慣れた開発者にとって、JavaScript はとても自然とは思えないものです。しかし Dojo を実装し、Dojo のクラス宣言機能を利用すると、クライアント・サイドの開発への移行が非常に容易になります。コンテキストをよく理解し、またコンテキストをいつ、どのように設定するのかをよく理解することによって、Java 開発者は数々の苦労を減らすことができ、また開発用のツールボックスに自信を持って JavaScript を追加できるようになります。
ダウンロード可能なリソース
関連トピック
- Dojo ツールキットを使い始めるためのすべての情報とリソースが DojoToolkit.org に用意されています。
- JavaScript の作成を支援する Eclipse ベースのツールについて学ぶために、「JavaScript Development Toolkit の紹介」(developerWorks、2008年5月) を読んでください。
- developerWorks の Ajax resource center には (Dojo を含めた) 他の Ajax 技術に関する情報が豊富に用意されています。
- Dojo API に関する完全な資料も用意されています。
- Dojo の素晴らしいコード・サンプルが Dojo campus にいくつか紹介されています。