MSDN マガジン > Home > 発行物 > 2007 > May >  JavaScript: オブジェクト指向の手法によって高度な Web アプリケーションを作成す...
JavaScript
オブジェクト指向の手法によって高度な Web アプリケーションを作成する
Ray Djajadinata

この記事の内容 : :
  • JavaScript はプロトタイプ ベ―スの言語
  • JavaScript によるオブジェクト指向プログラミング
  • JavaScript コーディングの秘訣
  • JavaScript の未来
この記事は次のテクノロジを使用しています:
JavaScript
最近、5 年間 Web アプリケーションの開発に携わっているというあるソフトウェア開発者に話を聞く機会がありました。彼女は 4 年半にわたり JavaScript を使用しており、JavaScript については高いスキルを持っていると自認していました。しかし、すぐにわかったのですが、実際には JavaScript の知識がほとんどなかったのです。とはいっても、それは彼女の責任ではありません。それだけ JavaScript が変わった言語だということです。C、C++、C# を知っているから、あるいは以前プログラミングの経験があるから、という理由だけで多くの人が得意だと思い込んでいる (私も最近までそうでした) のが、この JavaScript という言語です。
ある意味、このような思い込みはまったく根拠がないものでもありません。JavaScript で単純なことをするのは簡単です。きわめて敷居が低く、取っ付きやすい言語であり、詳しい知識がなくてもコーディングを始めることができます。JavaScript であれば、プログラマでない人でもホームページの便利なスクリプトをおそらく数時間で作成できてしまいます。
実際、私も最近まで、 JavaScript に関するなけなしの知識と MSDN® の DHTML リファレンスや C++ と C# の使用経験だけで何とかやってくることができました。現実の AJAX アプリケーションにかかわるようになって初めて、JavaScript の知識がいかに乏しいかを思い知らされました。この新世代の Web アプリケーションに特有の複雑さと対話性によって、JavaScript コードを書くには、従来とはまったく異なるアプローチが必要とされます。AJAX アプリケーションは本格的な JavaScript アプリケーションなのです。これまでの使い捨てのスクリプトを書くような方法ではとても対応できません。
コードベースの管理性と保守性を高める方法として多くの JavaScript ライブラリに採用されている一般的なアプローチの 1 つが、オブジェクト指向プログラミング (OOP) です。JavaScript は OOP をサポートしていますが、その内容は C++、C#、Visual Basic® などの Microsoft® .NET Framework 準拠の言語でのサポートとは大きく異なっています。そのため、これらの言語を使い慣れた開発者が JavaScript で OOP を行うと、初めのうちは違和感を覚え、戸惑うことがあります。この記事では、JavaScript がどのようにオブジェクト指向プログラミングをサポートし、それによっていかに効果的なオブジェクト指向開発を行うことができるかについて詳しく説明します。まず、オブジェクトの話から始めます (それ以外に何かあるでしょうか)。

JavaScript オブジェクトはディクショナリである
C++ や C# では、オブジェクトというとクラスや構造体のインスタンスを意味します。オブジェクトにはプロパティやメソッドが含まれていますが、それらはオブジェクトがどのテンプレート (クラス) からインスタンス化されたかによって異なります。このようなことは JavaScript オブジェクトにはありません。JavaScript では、オブジェクトは名前と値のペアの集まりに過ぎません。したがって、JavaScript オブジェクトは文字列キーが集まったディクショナリと考えることができます。オブジェクトのプロパティを取得し、設定するには、使い慣れた "." (ドット) 演算子、またはディクショナリの操作で一般に使用される "[]" 演算子を使用します。次のコードを参照してください。
var userObject = new Object();
userObject.lastLoginTime = new Date();
alert(userObject.lastLoginTime);    
このコードは次のコードとまったく同じ処理を実行します。
var userObject = {}; // equivalent to new Object()
userObject[“lastLoginTime”] = new Date();
alert(userObject[“lastLoginTime”]);
lastLoginTime プロパティは、次のように userObject の定義内に直接定義することもできます。
var userObject = { “lastLoginTime”: new Date() };
alert(userObject.lastLoginTime);
これは C# 3.0 のオブジェクト初期化コードとよく似ています。また、Python に詳しい人であれば、2 つ目および 3 つ目のコードで userObject をインスタンス化する方法が Python でディクショナリを指定する方法と同じであることに気付くはずです。異なる点は、JavaScript オブジェクト (ディクショナリ) が文字列キーだけを扱い、Python ディクショナリのようにハッシュ可能なオブジェクトは扱わないことだけです。
これらのコードを見ると、JavaScript オブジェクトの方が C++ オブジェクトや C# オブジェクトよりはるかに柔軟性が高いこともわかります。lastLoginTime プロパティは事前に宣言する必要がありません。userObject に lastLoginTime という名前のプロパティがなければ、lastLoginTime プロパティを宣言なしで userObject に追加することができます。JavaScript オブジェクトがディクショナリであることを考えると、これは驚くほどのことではありません。どのような場合でも、新しいキー (およびその値) をディクショナリに追加するだけだからです。
これで、オブジェクトのプロパティを作成できました。オブジェクトのメソッドについてはどうでしょうか。この点でも JavaScript は C++ や C# とは異なります。オブジェクトのメソッドについて理解するためには、まず JavaScript 関数について詳しく見ていく必要があります。

JavaScript の関数は優秀である
多くのプログラミング言語では、通常、関数とオブジェクトは異なるものとして扱われます。JavaScript では、この区別があいまいです。JavaScript 関数は実際には実行可能コードが関連付けられたオブジェクトです。次のような一般的な関数について考えてみます。
function func(x) {
    alert(x);
}
func(“blah”);
JavaScript では通常、関数をこのように定義します。ただし、この関数を次のように定義することもできます。この場合は、無名関数オブジェクトを作成し、それを変数 func に割り当てます。
var func = function(x) {
    alert(x);
};
func(“blah2”);
また、Function コンストラクタを使用して次のように定義することもできます。
var func = new Function(“x”, “alert(x);”);
func(“blah3”);
この例を見ると、関数が実際は関数呼び出しをサポートするオブジェクトに過ぎないことがわかります。この最後の Function コンストラクタを使用した関数定義方法は広く使用されているわけではありませんが、注目すべき可能性を秘めています。ご覧のとおり、この関数には Function コンストラクタへの String パラメータしか含まれていません。これは、実行時に任意の関数を構築できることを意味します。
JavaScript オブジェクトと同じように関数にもプロパティを設定または追加できることも、関数がオブジェクトであることの証拠です。
function sayHi(x) {
    alert(“Hi, “ + x + “!”);
}
sayHi.text = “Hello World!”;
sayHi[“text2”] = “Hello World... again.”;

alert(sayHi[“text”]); // displays “Hello World!”
alert(sayHi.text2); // displays “Hello World... again.”
関数についても、オブジェクトと同じように、変数に割り当てることができ、他の関数に引数として渡したり、他の関数の値として返したりすることができます。さらに、関数をオブジェクトのプロパティや配列の要素として保存することも可能です。その実例を図 1 に示します。
// assign an anonymous function to a variable
var greet = function(x) {
    alert(“Hello, “ + x);
};
greet(“MSDN readers”);

// passing a function as an argument to another
function square(x) {
    return x * x;
}
function operateOn(num, func) {
    return func(num);
}
// displays 256
alert(operateOn(16, square));

// functions as return values
function makeIncrementer() {
    return function(x) { return x + 1; };
}
var inc = makeIncrementer();
// displays 8
alert(inc(7));

// functions stored as array elements
var arr = [];
arr[0] = function(x) { return x * x; };
arr[1] = arr[0](2);
arr[2] = arr[0](arr[1]);
arr[3] = arr[0](arr[2]);
// displays 256
alert(arr[3]);

// functions as object properties
var obj = { “toString” : function() { return “This is an object.”; } };
// calls obj.toString()
alert(obj);
したがって、名前を選択し、その名前に関数を割り当てるだけで、簡単にメソッドをオブジェクトに追加することができます。たとえば、オブジェクトに 3 つのメソッドを定義するには、次のようにそれぞれのメソッド名に無名関数を割り当てます。
var myDog = {
    “name” : “Spot”,
    “bark” : function() { alert(“Woof!”); },
    “displayFullName” : function() {
        alert(this.name + “ The Alpha Dog”);
    },
    “chaseMrPostman” : function() { 
        // implementation beyond the scope of this article 
    }    
};
myDog.displayFullName(); 
myDog.bark(); // Woof!
関数 displayFullName 内でキーワード "this" がどのような働きをするかは、C++ や C# を使用する開発者であればだれもが知っています。このキーワードはメソッドの呼び出し元のオブジェクトを参照します (これは Visual Basic を使用する開発者にも馴染み深いものだと思います。Visual Basic には同じ働きをするキーワード "Me" があります)。上の例にある displayFullName で使用されている "this" の値は myDog オブジェクトです。ただし、"this" の値は静的ではありません。別のオブジェクトから呼び出すと、図 2 に示すように、"this" の値もそのオブジェクトに変わります。
function displayQuote() {
    // the value of “this” will change; depends on 
    // which object it is called through
    alert(this.memorableQuote);    
}

var williamShakespeare = {
    “memorableQuote”: “It is a wise father that knows his own child.”, 
    “sayIt” : displayQuote
};

var markTwain = {
    “memorableQuote”: “Golf is a good walk spoiled.”, 
    “sayIt” : displayQuote
};

var oscarWilde = {
    “memorableQuote”: “True friends stab you in the front.” 
    // we can call the function displayQuote
    // as a method of oscarWilde without assigning it 
    // as oscarWilde’s method. 
    //”sayIt” : displayQuote
};

williamShakespeare.sayIt(); // true, true
markTwain.sayIt(); // he didn’t know where to play golf

// watch this, each function has a method call()
// that allows the function to be called as a 
// method of the object passed to call() as an
// argument. 
// this line below is equivalent to assigning
// displayQuote to sayIt, and calling oscarWilde.sayIt().
displayQuote.call(oscarWilde); // ouch!
図 2 の最後の行は、関数をオブジェクトのメソッドとして呼び出すもう 1 つの方法を示しています。先ほど説明したとおり、JavaScript の関数はオブジェクトです。どの関数オブジェクトにも関数を 1 つ目の引数のメソッドとして呼び出す call というメソッドがあります。つまり、関数呼び出しの 1 つ目の引数として渡したオブジェクトが、その呼び出しの "this" の値となります。この手法は基本クラス コンストラクタを呼び出すときにも利用できます。この点については後ほど説明します。
1 つだけ注意が必要な点があります。所有オブジェクトが存在しない場合には "this" を含む関数を呼び出してはならないことです。そうすると、グローバル名前空間に損傷を与えることになります。そのような呼び出しでは、"this" が Global オブジェクトを参照するため、アプリケーションに深刻な悪影響を及ぼす可能性があるからです。たとえば、次のようなスクリプトは JavaScript のグローバル関数 isNaN の動作を変更してしまいます。このような "this" の使い方は避けてください。
alert(“NaN is NaN: “ + isNaN(NaN));

function x() {
    this.isNaN = function() { 
        return “not anymore!”;
    };
}
// alert!!! trampling the Global object!!!
x();

alert(“NaN is NaN: “ + isNaN(NaN));
ここまでオブジェクトの作成方法について説明してきましたが、その締めくくりとしてプロパティとメソッドについて考えます。これまで挙げたコードを見るとわかるとおり、プロパティとメソッドはオブジェクト定義内にハードコードされています。オブジェクトの作成について、より柔軟な操作が必要な場合は、どうすればよいのでしょうか。たとえば、オブジェクトのプロパティ値をパラメータに基づいて計算することが必要な場合もあります。ときにはオブジェクトのプロパティを実行後でなければ取得できない値に初期化することも必要になります。オブジェクトのインスタンスを複数作成しなければならない場合もあり、しかもこれは多くの場面で必要となります。
C# では、クラスを使用してオブジェクトをインスタンス化します。しかし、JavaScript は違います。クラスがないからです。次のセクションで説明しますが、"new" 演算子と組み合わせることにより関数がコンストラクタの役割を果たすことを利用します。

クラスはなくてもコンストラクタ関数がある
JavaScript OOP に関して最も奇妙に思える点は、前にも指摘したとおり、C# や C++ とは異なり、JavaScript にはクラスがないことです。C# で次のような処理を実行したとします。
Dog spot = new Dog();
この場合、オブジェクトが返されますが、それは Dog クラスのインスタンスです。ところが JavaScript にはそもそもクラスがありません。クラスと同じような効果を得るためには、次のようなコンストラクタ関数を定義します。
function DogConstructor(name) {
    this.name = name;
    this.respondTo = function(name) {
        if(this.name == name) {
            alert(“Woof”);        
        }
    };
}

var spot = new DogConstructor(“Spot”);
spot.respondTo(“Rover”); // nope
spot.respondTo(“Spot”); // yeah!
これで何が起きると思いますか。DogConstructor 関数定義は後回しにして、まず次のコードから見ていきます。
var spot = new DogConstructor(“Spot”);
"new" 演算子が行うことはいたって単純です。まず、新しい空のオブジェクトを作成します。次に、その直後の関数呼び出しが実行され、その関数の "this" に新たに作成された空のオブジェクトが設定されます。つまり、"new" 演算子の上の行は次の 2 行に似たものと考えることができます。
// create an empty object
var spot = {}; 
// call the function as a method of the empty object
DogConstructor.call(spot, “Spot”);
DogConstructor の内容を見るとわかるとおり、この関数を呼び出すと、その呼び出しにおいてキーワード “this” が参照するオブジェクトが初期化されます。これによって、オブジェクトのテンプレートを作成することができます。類似のオブジェクトを作成することが必要なときには、“new” を指定してコンストラクタ関数を呼び出すことで、完全に初期化されたオブジェクトを取得できます。クラスの働きとよく似ていると思いませんか。実際、JavaScript では、コンストラクタ関数を作成する場合、それに相当するクラスの名前を付けるのが一般的です。つまり、上の例のコンストラクタ関数には次のように Dog という名前を付けることができます。
// Think of this as class Dog
function Dog(name) {
    // instance variable 
    this.name = name;
    // instance method? Hmmm...
    this.respondTo = function(name) {
        if(this.name == name) {
            alert(“Woof”);        
        }
    };
}

var spot = new Dog(“Spot”);
この関数 Dog の定義では、name というインスタンス変数を定義しています。コンストラクタ関数として Dog を使用して作成したオブジェクトには、必ずインスタンス変数 name のコピーが含まれています (この変数は、前に指摘したとおり、オブジェクトのディクショナリへのエントリに過ぎません)。この点は想定どおりです。オブジェクトの状態を保持するためには、個々のオブジェクトにインスタンス変数の固有のコピーが必要になります。しかし、次の行を見ると、Dog のインスタンスもそれぞれが respondTo メソッドの固有のコピーを持っており、これでは無駄です。複数の Dog インスタンスで共有すれば、respondTo のインスタンスは 1 つしか必要ありません。この問題を解決するには、次のように respondTo の定義を Dog の定義の外側に置きます。
function respondTo() {
    // respondTo definition
}

function Dog(name) {
    this.name = name;
    // attached this function as a method of the object
    this.respondTo = respondTo;
}
これによって、Dog のインスタンス (つまりコンストラクタ関数 Dog によって作成されたインスタンス) は、すべて respondTo メソッドの 1 つのインスタンスを共有します。ただし、メソッドの数が増えるに従って管理が困難になっていきます。いずれコードベースはグローバル関数で一杯になります。クラスが増えるにつれて事態は悪化するばかりで、特に同じような名前のメソッドが多い場合は収拾がつかなくなります。このような問題を回避するのに効果的なのがプロトタイプ オブジェクトです。次のセクションでは、この点について説明します。

プロトタイプ
プロトタイプ オブジェクトは JavaScript によるオブジェクト指向プログラミングの中心的な概念の 1 つです。プロトタイプ オブジェクトという名前は、JavaScript ではオブジェクトを既存のオブジェクト (プロトタイプ) のコピーとして作成されるという考え方に由来します。プロトタイプ オブジェクトのプロパティとメソッドは、そのプロトタイプのコンストラクタによって作成されたオブジェクトのプロパティとメソッドとなります。つまり、オブジェクトはプロトタイプのプロパティとメソッドを継承するということができます。次のような Dog オブジェクトを作成するとします。
var buddy = new Dog(“Buddy“);
buddy が参照するオブジェクトはプロトタイプのプロパティとメソッドを継承します。とはいっても、どのように継承するのかは、このプロトタイプを作成する 1 行だけでは、はっきりとはわかりません。オブジェクト buddy のプロトタイプはコンストラクタ関数 (ここでは関数 Dog) のプロパティから派生します。
JavaScript では、すべての関数に "prototype" というプロパティがあり、このプロパティがプロトタイプ オブジェクトを参照します。一方、プロトタイプ オブジェクトには "constructor" というプロパティがあり、このプロパティは関数を逆参照します。このように一種の循環参照を形成します。この循環関係をわかりやすく示したのが図 3 です。
図 3 どの関数のプロトタイプにも constructor プロパティがある 
ここで、関数 (上の例では Dog) を使用して "new" 演算子を持つオブジェクトを作成すると、そのオブジェクトは Dog.prototype のプロパティを継承します。図 3 を見ると、Dog.prototype オブジェクトに constructor プロパティがあり、関数 Dog を逆参照していることがわかります。そこから推すと、Dog.prototype から派生した Dog オブジェクトにも関数 Dog を逆参照する constructor プロパティがあるように思えます。この点は図 4 に示すコードで確認できます。コンストラクタ関数、プロトタイプ オブジェクト、それらによって作成されたオブジェクトの関係を図 5 に示します。
var spot = new Dog(“Spot”);

// Dog.prototype is the prototype of spot
alert(Dog.prototype.isPrototypeOf(spot));

// spot inherits the constructor property
// from Dog.prototype
alert(spot.constructor == Dog.prototype.constructor);
alert(spot.constructor == Dog);

// But constructor property doesn’t belong
// to spot. The line below displays “false”
alert(spot.hasOwnProperty(“constructor”));

// The constructor property belongs to Dog.prototype
// The line below displays “true”
alert(Dog.prototype.hasOwnProperty(“constructor”));
図 5 インスタンスはプロトタイプを継承する 
図 4 で hasOwnProperty メソッドと isPrototypeOf メソッドを呼び出していることに気付いた方もいるかもしれません。これらのメソッドはどこから来たのでしょうか。Dog.prototype のメソッドではありません。事実、Dog.prototype や Dog のインスタンスでは他にも toString、toLocaleString、valueOf などのメソッドを呼び出すことができますが、これらはいずれも Dog.prototype のメソッドではありません。実は、.NET Framework にすべてのクラスの基本クラスである System.Object があるように、JavaScript にもすべてのプロトタイプの基本プロトタイプとなる Object.prototype があります (Object.prototype のプロトタイプは null です)。
次の例では、Dog.prototype が 1 つのオブジェクトであることに注意してください。このオブジェクトは Object コンストラクタ関数の呼び出しによって作成されますが、その処理は表面には現れません。
Dog.prototype = new Object();
Dog のインスタンスが Dog.prototype を継承するのとまったく同じように、Dog.prototype も Object.prototype を継承します。それによって、Dog のすべてのインスタンスが Object.prototype のメソッドとプロパティも継承することになります。
どの JavaScript オブジェクトもプロトタイプのチェーンを継承し、すべてのプロトタイプが Object.prototype に帰着します。ただし、これまで見てきた継承はライブ オブジェクト間の継承です。一般に考えられているような、クラスの宣言によって形成されるクラス間の継承とは異なります。したがって、JavaScript の継承はクラス間の継承よりはるかに動的なものとなります。JavaScript の継承は次のような単純なアルゴリズムによって確立されます。あるオブジェクトのプロパティまたはメソッドにアクセスすると、JavaScript がそのオブジェクトに該当するプロパティまたはメソッドが定義されているかどうかをチェックします。定義されていない場合は、そのオブジェクトのプロトタイプをチェックします。プロトタイプにも定義されていない場合は、そのプロトタイプのプロトタイプをチェックします。このようにして、最終的に Object.prototype までチェックが進みます。図 6 に、この解決プロセスを示します。
図 6 プロトタイプ チェーンで toString() メソッドを解決する (画像を拡大するには、ここをクリックします)
このように JavaScript はプロパティへのアクセスやメソッドの呼び出しを動的に解決するため、次のような影響をもたらします。
  • プロトタイプ オブジェクトに加えた変更は、直ちにそのプロトタイプを継承するオブジェクトで認識可能になります。オブジェクトの作成後に行われた変更についても同様です。
  • あるオブジェクトにプロパティまたはメソッド X を定義すると、そのオブジェクトのプロトタイプでは同じ名前のプロパティまたはメソッドが無効になります。たとえば、Dog.prototype に toString メソッドを定義することによって、Object.prototype の toString をオーバーライドできます。
  • 変更は、プロトタイプから派生オブジェクトへの 1 方向のみに伝達され、その逆の方向には伝達されません。
これらの影響を図 7 に示します。図 7 には、不要なメソッド インスタンスが発生する問題を早期に解決する方法も示してあります。オブジェクトごとに関数オブジェクトのインスタンスを作成する代わりに、メソッドをプロトタイプの内部に置くことによって複数のオブジェクトで共有することができます。この例では、spot の toString メソッドをオーバーライドするまで、rover と spot が getBreed メソッドを共有します。toString メソッドをオーバーライドした後は、spot には getBreed メソッドの固有のバージョンがありますが、rover オブジェクトとそれ以降に新しい GreatDane で作成されたオブジェクトは依然として GreatDane.prototype オブジェクトに定義された getBreed メソッドのインスタンスを共有します。
function GreatDane() { }

var rover = new GreatDane();
var spot = new GreatDane();

GreatDane.prototype.getBreed = function() {
    return “Great Dane”;
};

// Works, even though at this point
// rover and spot are already created.
alert(rover.getBreed());

// this hides getBreed() in GreatDane.prototype
spot.getBreed = function() {
    return “Little Great Dane”;
};
alert(spot.getBreed()); 

// but of course, the change to getBreed 
// doesn’t propagate back to GreatDane.prototype
// and other objects inheriting from it,
// it only happens in the spot object
alert(rover.getBreed());

静的なプロパティとメソッド
インスタンスではなくクラスに関連付けられたプロパティやメソッド、つまり静的なプロパティやメソッドが必要になることがあります。このような場合、JavaScript では簡単に対応できます。関数がオブジェクトであり、そのプロパティとメソッドを必要に応じて設定できるからです。JavaScript ではコンストラクタ関数がクラスに相当するため、次のようにコンストラクタ関数にメソッドとプロパティを設定するだけで、静的なメソッドとプロパティを追加することができます。
    function DateTime() { }

    // set static method now()
    DateTime.now = function() {
        return new Date();
    };

    alert(DateTime.now());
JavaScript で静的なメソッドを呼び出す構文は、C# の場合と実質的に同じです。これは驚くほどのことではありません。事実上、コンストラクタ関数とクラスの名前は一致するからです。したがって、クラスもあれば、パブリックなプロパティやメソッドもあり、さらに静的なプロパティやメソッドまであるということです。これ以外に必要なものはあるでしょうか。もちろん、プライベート メンバは必要です。しかし、JavaScript にはプライベート メンバのネイティブ サポートは含まれていません (さらに言うと、プロテクト メンバもサポートしていません)。オブジェクトのすべてのプロパティとメソッドは、だれもがアクセス可能です。クラスにプライベート メンバを定義することは可能です。ただし、そのためにはクロージャについて知っておく必要があります。

クロージャ
私は自分から進んで JavaScript を勉強しようと思ったわけではありません。JavaScript が使用できなくては本格的な AJAX アプリケーションに取り組むうえで準備不足であるとわかり、急いで習得せざるを得なかったのです。初めのうちは、プログラミングのレベルを少し落としたような印象でした (何と言っても JavaScript です。C++ を使っている友人だったら何と言うでしょうか)。しかし、当初あった抵抗感を克服すると、JavaScript が実は強力で表現力に富むコンパクトな言語であることがわかってきました。JavaScript には他のよく使われる言語がサポートし始めたばかりの機能も含まれています。
JavaScript の高度な機能の 1 つにクロージャのサポートがあります。C# 2.0 は匿名メソッドを介してクロージャをサポートしています。クロージャとは、内側の関数 (C# では内側の匿名メソッド) が外側の関数のローカル変数にバインドされたときに発生する実行時の現象をいいます。当然ながら、内側の関数が何らかの形で外側の関数の外部でアクセス可能にならなければ、この現象はあまり意味がありません。例を使うとわかりやすくなります。
100 を超える数だけを取得し、他の数は除外するという、単純な基準のフィルタ処理を一連の数に適用する必要があるとします。この場合、図 8 のような関数を定義できます。
function filter(pred, arr) {
    var len = arr.length;
    var filtered = []; // shorter version of new Array();
    // iterate through every element in the array...
    for(var i = 0; i < len; i++) {
        var val = arr[i];
        // if the element satisfies the predicate let it through
        if(pred(val)) {
            filtered.push(val);
        }
    }
    return filtered;
}

var someRandomNumbers = [12, 32, 1, 3, 2, 2, 234, 236, 632,7, 8];
var numbersGreaterThan100 = filter(
    function(x) { return (x > 100) ? true : false; }, 
    someRandomNumbers);

// displays 234, 236, 632
alert(numbersGreaterThan100);
その後、別のフィルタ基準を作成する必要が生じたとします。たとえば、300 より大きな数だけを取得する場合は、次のようなコードを作成します。
var greaterThan300 = filter(
    function(x) { return (x > 300) ? true : false; }, 
    someRandomNumbers);
さらに、50 より大きな数、25 より大きな数、10 より大きな数、600 より大きな数などの取得が必要になることも考えられます。ここで、勘の良い人であれば、どの基準も述語は同じ "より大きな数" であることに気付きます。異なるのは数だけです。このような場合は、次のような関数によって数を変数化することができます。
function makeGreaterThanPredicate(lowerBound) {
    return function(numberToCheck) {
        return (numberToCheck > lowerBound) ? true : false;
    };
}
これによって、次のようなことが可能になります。
var greaterThan10 = makeGreaterThanPredicate(10);
var greaterThan100 = makeGreaterThanPredicate(100);
alert(filter(greaterThan10, someRandomNumbers));
alert(filter(greaterThan100, someRandomNumbers));
関数 makeGreaterThanPredicate が返す内側の無名関数をよく見てください。この内側の無名関数は、makeGreaterThanPredicate に渡された引数 lowerBound を使用します。通常のスコープ ルールでは、lowerBound は makeGreaterThanPredicate が終了するとスコープ外となります。ところが、この例では、内側の無名関数は makeGreaterThanPredicate の終了後も lowerBound を保持します。これがクロージャと呼ばれる現象で、内側の関数がその定義された環境 (外側の関数の引数とローカル変数) を閉じ込めることを意味します。
クロージャは、初めのうちはそれほど役に立つとは思えないかもしれません。しかし、適切な方法で使用すると、アイデアをコードとして具体化する際に注目すべき新たな可能性をもたらします。JavaScript でクロージャを使用する場合、その最も面白い用途の 1 つとして、クラスのプライベート変数をシミュレートすることがあります。

プライベート プロパティをシミュレートする
ではここで、プライベート メンバをシミュレートするうえでクロージャがどのように役立つのかを説明します。関数のローカル変数は、通常はその関数の外側からアクセスすることはできません。関数が終了すると、その関数のローカル変数はあらゆる意味で効力を失います。ただし、内側の関数のクロージャがローカル変数をキャプチャすると、変数はその後も存続できます。この点が JavaScript でプライベート プロパティをシミュレートするときの鍵となります。次の Person クラスについて考えてみましょう。
function Person(name, age) {
    this.getName = function() { return name; };
    this.setName = function(newName) { name = newName; };
    this.getAge = function() { return age; };
    this.setAge = function(newAge) { age = newAge; };
}
引数の name と age は、コンストラクタ関数 Person のローカル変数です。Person が終了すると、name と age は通常であれば効力を失いますが、Person のインスタンスのメソッドとして割り当てられた 4 つの内側の関数がキャプチャすることにより、それ以降も使用可能になります。ただし、name と age にアクセスできるのは、これら 4 つのメソッドだけです。これによって、次のような処理が可能です。
var ray = new Person(“Ray”, 31);
alert(ray.getName());
alert(ray.getAge());
ray.setName(“Younger Ray”);
// Instant rejuvenation!
ray.setAge(22);
alert(ray.getName() + “ is now “ + ray.getAge() + 
      “ years old.”);
コンストラクタで初期化されないプライベート メンバは、次のようにコンストラクタ関数のローカル変数として定義することができます。
function Person(name, age) {
    var occupation;
    this.getOccupation = function() { return occupation; };
    this.setOccupation = function(newOcc) { occupation = 
                         newOcc; };
  
    // accessors for name and age    
}
これらのプライベート メンバは、C# のプライベート メンバとは働きが少し異なります。C# では、クラスのパブリック メソッドが同じクラスのプライベート メンバにアクセスできます。それに対し、JavaScript では、プライベート メンバにアクセスできるのは、クロージャ内にそれらのプライベート メンバがあるメソッドだけです (このようなメソッドは、通常のパブリック メソッドとは異なるため、特権メソッドと呼ばれます)。したがって、Person のパブリック メソッド内でプライベート メンバにアクセスするためには、次のように特権アクセサ メソッドを使用する必要があります。
Person.prototype.somePublicMethod = function() {
    // doesn’t work!
    // alert(this.name);
    // this one below works
    alert(this.getName());
};
Douglas Crockford は、クロージャを使用してプライベート メンバをシミュレートする手法を発見 (あるいは発表) した最初の人物として知られています。彼の Web サイト javascript.crockford.com には JavaScript に関する情報が大量に掲載されています。JavaScript に興味のある開発者の方はぜひチェックしてみてください。

クラスの継承
コンストラクタ関数とプロトタイプ オブジェクトを使用すると JavaScript でクラスをシミュレートできることについては、既に説明しました。プロトタイプ チェーンによって、すべてのオブジェクトで Object.prototype の共通のメソッドを使用できることも説明しました。クロージャによってプライベート メンバをシミュレートする方法も紹介しました。しかし、何かが抜けています。派生クラスを作成する方法には触れていませんでした。C# では頻繁に行う作業です。クラスを継承することは、C# ではコロンを 1 つ入力するような単純な作業ですが、残念ながら JavaScript ではそれほど簡単ではありません。その一方で、JavaScript は柔軟性に富んでおり、多くのクラス継承方法が存在します。
たとえば、図 9 のように、基本クラス Pet とその派生クラス Dog があるとします。このようなクラスを JavaScript ではどのように定義するのでしょうか。Pet クラスは簡単です。前に説明したとおり、次のように定義できます。
図 9 クラス 
// class Pet
function Pet(name) {
    this.getName = function() { return name; };
    this.setName = function(newName) { name = newName; };
}

Pet.prototype.toString = function() {
    return “This pet’s name is: “ + this.getName();
};
// end of class Pet

var parrotty = new Pet(“Parrotty the Parrot”);
alert(parrotty);
では、Pet の派生クラス Dog はどのように作成するのでしょうか。図 9 を見るとわかるとおり、Dog には Pet にないプロパティ breed があり、Pet の toString メソッドをオーバーライドしています (メソッド名とプロパティ名については、C# では Pascal 形式の使用が推奨されていますが、JavaScript ではキャメル形式を使用するように規定されています)。これをどのように定義するのかを図 10 に示します。
// class Dog : Pet 
// public Dog(string name, string breed)
function Dog(name, breed) {
    // think Dog : base(name) 
    Pet.call(this, name);
    this.getBreed = function() { return breed; };
    // Breed doesn’t change, obviously! It’s read only.
    // this.setBreed = function(newBreed) { name = newName; };
}

// this makes Dog.prototype inherits
// from Pet.prototype
Dog.prototype = new Pet();

// remember that Pet.prototype.constructor
// points to Pet. We want our Dog instances’
// constructor to point to Dog.
Dog.prototype.constructor = Dog;

// Now we override Pet.prototype.toString
Dog.prototype.toString = function() {
    return “This dog’s name is: “ + this.getName() + 
        “, and its breed is: “ + this.getBreed();
};
// end of class Dog

var dog = new Dog(“Buddy”, “Great Dane”);
// test the new toString()
alert(dog);

// Testing instanceof (similar to the is operator)
// (dog is Dog)? yes
alert(dog instanceof Dog);
// (dog is Pet)? yes
alert(dog instanceof Pet);
// (dog is Object)? yes
alert(dog instanceof Object);
ここで使用されているプロトタイプ置換手法によってプロトタイプ チェーンが正しく設定されるため、instanceof によるテストが C# を使用した場合と同じように実行されます。また、特権メソッドも期待どおりに動作します。

名前空間をシミュレートする
C++ や C# では、名前の競合が発生する可能性を最小限に抑えるために名前空間を使用します。.NET Framework では、たとえば Microsoft.Build.Task.Message クラスと System.Messaging.Message クラスを区別するような場合に名前空間を利用できます。JavaScript には、名前空間をサポートする固有の言語機能はありませんが、オブジェクトを使用することによって簡単に名前空間をシミュレートすることができます。ここで、JavaScript ライブラリの作成を例にとって考えます。グローバルな関数とクラスを定義する代わりに、それらを次のように名前空間にラップすることができます。
var MSDNMagNS = {};

MSDNMagNS.Pet = function(name) { // code here };
MSDNMagNS.Pet.prototype.toString = function() { // code };

var pet = new MSDNMagNS.Pet(“Yammer”);
名前空間の 1 レベルは一意でなくてもよいため、次のような入れ子になった名前空間を作成できます。
var MSDNMagNS = {};
// nested namespace “Examples”
MSDNMagNS.Examples = {}; 

MSDNMagNS.Examples.Pet = function(name) { // code };
MSDNMagNS.Examples.Pet.prototype.toString = function() { // code };

var pet = new MSDNMagNS.Examples.Pet(“Yammer”);
ご想像どおり、このような入れ子になった長い名前空間をいくつも入力していたら、すぐに嫌気がさしてきます。さいわい、ライブラリのユーザーは名前空間に次のような短い別名を簡単に付けることができます。
// MSDNMagNS.Examples and Pet definition...

// think “using Eg = MSDNMagNS.Examples;” 
var Eg = MSDNMagNS.Examples;
var pet = new Eg.Pet(“Yammer”);
alert(pet);
Microsoft AJAX Library のソース コードを参照すると、ライブラリの作成者が同じような手法で名前空間を実装していることがわかります (静的メソッド Type.registerNamespace の実装を探してみてください)。詳細については、「OOP と ASP.NET AJAX」のリンクを参照してください。

JavaScript ではこのようにコーディングが必要なのか
JavaScript がオブジェクト指向プログラミングを適切にサポートしていることについては既に説明しました。JavaScript はプロトタイプ ベースの言語として設計されていますが、柔軟性が高く強力であるため、他のよく使われる言語で一般的なクラス ベースのプログラミング スタイルにも対応できます。ただし、ここで疑問が生じます。JavaScript でも、そのようなスタイルでコーディングを行ってよいのでしょうか。JavaScript でも C# や C++ の場合と同じコーディング スタイルを使用し、JavaScript にはない機能をシミュレートする巧妙な方法を編み出していけばよいのでしょうか。プログラミング言語はそれぞれに違いがあり、ある言語のベスト プラクティスが別の言語でもベスト プラクティスであるとは限りません。
既に説明したとおり、JavaScript では (クラスがクラスを継承するのではなく) オブジェクトがオブジェクトを継承します。したがって、静的な継承階層を使用して多くのクラスを作成することが JavaScript のやり方ではない可能性があります。おそらく、Douglas Crockford が自分の記事「JavaScript におけるプロトタイプ的継承」で述べているように、JavaScript のプログラミング手法は、プロトタイプ オブジェクトを作成し、次のような簡単なオブジェクト関数を使用して新しいオブジェクトを作成するというものです。つまり、新しいオブジェクトは元のオブジェクトを継承します。
    function object(o) {
        function F() {}
        F.prototype = o;
        return new F();
    }
JavaScript のオブジェクトは柔軟性に富んでいるため、作成後に新たなフィールドやメソッドを追加し、簡単にオブジェクトを拡張することができます。
このこと自体はまったく問題がないとしても、世界的に見て開発者の大部分がクラス ベースのプログラミングの方に慣れ親しんでいることは否定できない事実です。実際、クラス ベースのプログラミングは JavaScript にも浸透してきています。近々発表される ECMA-262 (JavaScript の公式仕様) の第 4 版によると、JavaScript 2.0 には正式なクラスが導入されるようです。このことから、JavaScript もクラス ベースの言語に移行しつつあると言えます。ただし、JavaScript 2.0 が普及するまでには、おそらく数年かかるでしょう。それまでの間は、現行の JavaScript について、プロトタイプ ベースとクラス ベースの両方のスタイルで JavaScript のコードを読み書きできるだけの知識を身に付けることが大切です。

大局的に見る
対話的なクライアント集約型 AJAX アプリケーションが急増したことにより、.NET 開発者の間では利用価値の高いツールの 1 つとして JavaScript が急浮上してきました。ただし、そのプロトタイプ ベースという特性は、C++、C#、Visual Basic などの言語に慣れている開発者を初めのうちは驚かせることになったようです。私にとって JavaScript の習得は実りあるものでしたが、その過程で嫌気がさしたことがまったくなかったわけではありません。皆さんが JavaScript を習得するうえで、この記事が役に立ってくれればさいわいです。それが私の目指したことだからです。
OOP と ASP.NET AJAX
ASP.NET AJAX に実装されている OOP は、ここで説明した標準的な実装とは多少異なります。その主な理由は 2 つあります。1 つはリフレクションの点で ASP.NET AJAX バージョンの方が多くの可能性を持っていることで (リフレクションは XML-Script などの宣言構文やパラメータ検証などに必要です)、もう 1 つは ASP.NET AJAX がプロパティ、イベント、列挙、インターフェイスなどの .NET 開発者に馴染み深い構文を JavaScript に変換することを目的としていることです。
JavaScript の現行バージョンでは .NET 開発者が慣れ親しんでいる OOP の主要な概念がいくつか欠如していますが、そのほとんどは ASP.NET AJAX でエミュレートされています。
クラスには、名前付け規則に基づくプロパティ アクセサ (後で例を示します) や .NET が提供するパターンを忠実に再現するパターンに基づくマルチキャスト イベントを定義できます。プライベート変数は、アンダースコアで始まるメンバはプライベート メンバであるという規則に基づいています。真のプライベート変数が必要となることはめったになく、この規則によってプライベート変数をデバッガで検査することができます。通常のアヒル型定義 (アヒルのように歩き、アヒルのように鳴くものはアヒルであり、そうでないとしてもアヒルとして扱うことができるという考え方) を越えた型チェック シナリオを可能にするインターフェイスも導入されています。

クラスとリフレクション
JavaScript には、関数の名前を調べる機能はありません。関数の名前を調べることができたとしても、ほとんどの場合、役には立ちません。クラスを作成するには、一般に名前空間で定義された変数に無名関数を割り当てるからです。実際に型名を構成しているのは、この変数の完全修飾名です。この変数はどこからもアクセスできず、そのコンストラクタ関数は何の情報も持っていません。この問題を解決し、ASP.NET AJAX が JavaScript クラスに対する充実したリフレクション機能を提供するためには、型名を登録する必要があります。
ASP.NET AJAX のリフレクション API は、組み込み型、クラス、インターフェイス、名前空間、列挙など、どのような型にも適用でき、isInstanceOfType や inheritsFrom など、実行時にクラス階層を検査する .NET Framework 型の関数を備えています。ASP.NET AJAX にはデバッグ モードで実行できる型チェック機能もあり、バグの早期発見に役立ちます。

クラス階層の登録と基本クラスの呼び出し
ASP.NET AJAX でクラスを定義するためには、クラスのコンストラクタを変数に割り当てる必要があります (コンストラクタが基本クラスを呼び出す方法に注目してください)。
MyNamespace.MyClass = function() {
    MyNamespace.MyClass.initializeBase(this);
    this._myProperty = null;
}
Then, you need to define the class members itself in its prototype:

MyNamespace.MyClass.prototype = {
    get_myProperty: function() { return this._myProperty;},
    set_myProperty: function(value) { this._myProperty = value; },
    doSomething: function() {
        MyNamespace.MyClass.callBaseMethod(this, “doSomething”);
        /* do something more */
    }
}
最後にクラスを登録します。
MyNamespace.MyClass.registerClass(
    “MyNamespace.MyClass “, MyNamespace.BaseClass);
コンストラクタやプロトタイプ階層を管理する必要はありません。その役割は registerClass 関数が担っています。

Bertrand Le Roy は ASP.NET AJAX チームのソフトウェア設計エンジニア II です。


Ray Djajadinataは Barclays Capital Singapore で AJAX アプリケーションの開発に夢中で取り組んでいます。Ray の連絡先は、ray.djajadinata@gmail.com です。

Page view tracker