最終回となる今回は、JavaScriptでの本格的コーディングに欠かせない<プロトタイプ・ベースのオブジェクト指向>について解説。
JavaScriptが、いま注目を浴びている。
JavaScriptがこれだけの注目を浴びた理由の1つとして、Ajax技術の登場とも相まって、JavaScriptに対する確かな理解の必要性が高まったという事情は否定できない。しかし、それだけでは説明できない急速な注目の理由として、もう1つ、JavaScriptという言語そのものが持つユニークさが開発者の目を引いたという点は看過できないだろう。
もっとも、このユニークさは同時に、多くの開発者が感じているJavaScriptに対する苦手意識と同義でもある。これまでVisual BasicやC#、Javaといった言語でオブジェクト指向構文になじんできた開発者にとって、JavaScriptのオブジェクト指向構文はいかにも奇異なものに映るのだ。ようやくクラスという概念を理解した開発者が、JavaScriptという言語の背後にたびたび見え隠れする「プロトタイプ・ベースのオブジェクト指向」というキーワードに、そもそもの敬遠感を抱いているという事情もあるだろう。
しかし、JavaScriptにおける「プロトタイプ・ベースのオブジェクト指向」は何ら目新しい概念ではない。ごくごく単純化していってしまえば、プロトタイプとは「より縛りの弱いクラス」のようなものと思っていただければよいだろう。
連載最終回となる今回は、JavaScriptで本格的なプログラミングを行うのに欠かせない――そして、JavaScriptに対する苦手意識の根幹でもある(と思われる)「プロトタイプ・ベースのオブジェクト指向」構文について解説する。
なお、本論に入るに当たって、1点のみ注意していただきたい点がある。第1回でも述べたように、JavaScriptでは厳密な意味でのクラスという概念は存在しない。しかし、本稿を読まれている読者の多くはクラス・ベースのオブジェクト指向構文になじんでいると思われることから、ここではJavaScriptにおいて「クラス的な役割を持つ存在」を、便宜上、「クラス」と呼ぶものとする。ご了承いただきたい。
JavaScriptにおけるオブジェクト指向構文について理解するには、抽象的な解説を重ねるよりも、まずは具体的なクラスを実際に作成してみた方が話が早いだろう。以下は、JavaScriptで中身を持たない、最も単純なクラスを定義した例だ。
var Animal = function() {};
変数Animalに対して、空の関数リテラルを代入しているだけのコードだ。「これがクラス?」と思われた方もいるかもしれないが、そのとおり、これがJavaScriptにおけるクラスなのである。れっきとしたクラスである証拠に、
var anim = new Animal();
と、クラス・ベースのオブジェクト指向構文でもおなじみのnew演算子を利用して、実際にインスタンス化を行ってみると、確かに正しく(エラーなども出ずに)実行できることが確認できる。ここではまず、
JavaScriptでは関数オブジェクトにクラスとしての役割を与えている
という点を覚えておこう。
■コンストラクタとプロパティ
コンストラクタとは、オブジェクトを生成する際に自動的に呼び出される関数(メソッド)のこと。クラス・ベースのオブジェクト指向構文を少しでもかじったことのある方ならば、これはごく耳になじんだキーワードの1つであるはずだ。
リスト1で記述したAnimal関数は、new演算子によって呼び出され、オブジェクトを生成するという意味で、厳密には「クラス」そのものというよりも「コンストラクタ」と呼ぶのがより正しいだろう。
コードを見ても分かるように、JavaScriptにおいては、コンストラクタと(普通の)関数との間に本質的な違いはない。関数として呼び出すのか、それともnew演算子によって呼び出すかによって、関数の振る舞いが変わるわけである。もっとも、(構文規則ではないが)実際のコードで、通常の関数とコンストラクタとが区別できないのは不便であるので、一般的にはコンストラクタ(クラス)名は大文字で始めるのが好ましい。
さて、先ほどのリスト1では空のコンストラクタを定義したわけであるが、通常、コンストラクタの中では生成するオブジェクトを初期化するためのコードを記述するのが一般的だ。具体的には、オブジェクト共通で利用するプロパティ(メンバ変数)などを定義するのがコンストラクタの役割なのである。
以下は、リスト1で作成したAnimalクラスにname/sexプロパティを追加した例だ。
var Animal = function(name, sex) {
this.name = name;
this.sex = sex;
}
var anim = new Animal("トクジロウ", "オス");
window.alert(anim.name + ":" + anim.sex); // 「トクジロウ:オス」
ここで注目していただきたいのは、コンストラクタの中のthisキーワードだ。コンストラクタとして関数オブジェクトを呼び出した場合、thisキーワードは新たに生成されるオブジェクトを表すことになる。そして、
this.プロパティ名 = 値
のように記述することで、オブジェクトにプロパティを追加することができるというわけだ。実際、リスト2の例でも、コンストラクタで設定されたname/sexプロパティが、作成したオブジェクトから正しく参照できることが確認できるはずだ。
[注意]コンストラクタに戻り値は不要
もう1点、コンストラクタを定義する場合のささやかな注意点がある。というのも、コンストラクタでは「戻り値を返す必要がない」という点だ。クラス・ベースのオブジェクト指向構文に慣れていればごく当たり前のポイントではあるが、プロトタイプ・ベースのオブジェクト指向構文でもこの点は同様であるので、あらためて注意しておきたい。コンストラクタ関数の役割は、あくまでこれから生成するオブジェクトの初期化を行うことであって、オブジェクトそのものを返すことではない。
■メソッド − コンストラクタによる定義 −
いまさらいうまでもなく、オブジェクトの構成は大きく「データ」と「手続き」とに分類できる。先ほどは、プロパティ(メンバ変数)を定義することでオブジェクトの「データ」を定義したので、当然の流れとして、次は「手続き」の部分――「メソッド(メンバ関数)」を定義してみることにしよう。
以下は、先ほどのAnimalクラスにtoStringという名前でメソッドを追加した例だ。
var Animal = function(name, sex) {
this.name = name;
this.sex = sex;
this.toString = function() {
window.alert(this.name + " " + this.sex);
};
}
var anim = new Animal("トクジロウ", "オス");
anim.toString(); // 「トクジロウ オス」
JavaScriptには厳密な意味でのメソッドという概念はない。このリスト3を見ても分かるように、「値として関数オブジェクトが渡されたプロパティがメソッドと見なされる」わけだ。ここではtoStringという名前のプロパティに匿名関数を引き渡すことで、toStringというメソッドを追加したことになる。
果たして、toStringメソッドを実行してみると、確かに「トクジロウ オス」というメッセージが返されることが確認できるはずだ。
■メソッド − インスタンスへの追加 −
もっとも、JavaScriptではメソッドをあらかじめコンストラクタで定義できるばかりではない。インスタンス化されたオブジェクトに対しても、後からメンバを追加できるのがJavaScriptの特徴だ(これをJavaScriptの「動的性質」と呼ぶ)。
そのJavaScriptの動的性質を利用することで、先ほどのリスト3は以下のように書き換えることも可能だ。
var Animal = function(name, sex) {
this.name = name;
this.sex = sex;
}
var anim = new Animal("トクジロウ", "オス");
anim.toString = function() {
window.alert(this.name + " " + this.sex);
};
anim.toString(); // 「トクジロウ オス」
先ほど同様、toStringメソッドが正しく動作していることが確認できるはずだ。ただし、インスタンスに対して動的にメソッドを追加した場合には、注意も必要だ。
以下に、Animalクラスに対するインスタンスを2つ登場させた例を見てみよう。
var Animal = function(name, sex) {
this.name = name;
this.sex = sex;
}
var anim = new Animal("トクジロウ", "オス");
anim.toString = function() {
window.alert(this.name + " " + this.sex);
};
anim.toString(); // 「トクジロウ オス」
var anim2 = new Animal("リンリン", "メス");
anim2.toString(); // エラー発生
インスタンスanim2からtoStringメソッドを呼び出そうとした時点で、エラーが発生することが確認できる。Internet Explorerでは、恐らく「オブジェクトでサポートされていないプロパティまたはメソッドです。」というエラーメッセージが出力されるはずだ。
このことから、インスタンスへのメソッドの追加はあくまでインスタンス(ここではanim)への追加であって、「大本であるクラスへの追加ではない」ことが理解できる。つまり、クラス・ベースのオブジェクト指向言語においては、あるクラスを基に作成されたインスタンスは必ず同じメンバを持つはずであるが、JavaScriptでは同一の“クラス”を基に作成されたインスタンスでも異なるメンバを持つ可能性がある、ということなのである。これが、先ほどプロトタイプ・ベースのオブジェクト指向が、クラス・ベースのそれよりも「縛りが弱い」と述べた理由でもある*1。
*1 ちなみに、ここではメンバを追加する例について述べたが、インスタンス、あるいはコンストラクタ経由で追加したメンバはdelete演算子(後述)で削除することも可能だ。
いずれにせよ、ここでは、
インスタンスに追加したメンバは、
(オブジェクト共通ではなく)そのインスタンスのみで有効である
という点を押さえておこう。
Copyright© Digital Advantage Corp. All Rights Reserved.