連載:Ajax時代のJavaScriptプログラミング再入門

第4回 JavaScriptでオブジェクト指向プログラミング

山田 祥寛(http://www.wings.msn.to/
2007/09/25
Page1 Page2 Page3 Page4


Back Issue
1
JavaScriptの復権
2 JavaScriptの関数をマスターしよう
3 変数の宣言とスコープ

 JavaScriptが、いま注目を浴びている。

 JavaScriptがこれだけの注目を浴びた理由の1つとして、Ajax技術の登場とも相まって、JavaScriptに対する確かな理解の必要性が高まったという事情は否定できない。しかし、それだけでは説明できない急速な注目の理由として、もう1つ、JavaScriptという言語そのものが持つユニークさが開発者の目を引いたという点は看過できないだろう。

 もっとも、このユニークさは同時に、多くの開発者が感じているJavaScriptに対する苦手意識と同義でもある。これまでVisual BasicやC#、Javaといった言語でオブジェクト指向構文になじんできた開発者にとって、JavaScriptのオブジェクト指向構文はいかにも奇異なものに映るのだ。ようやくクラスという概念を理解した開発者が、JavaScriptという言語の背後にたびたび見え隠れする「プロトタイプ・ベースのオブジェクト指向」というキーワードに、そもそもの敬遠感を抱いているという事情もあるだろう。

 しかし、JavaScriptにおける「プロトタイプ・ベースのオブジェクト指向」は何ら目新しい概念ではない。ごくごく単純化していってしまえば、プロトタイプとは「より縛りの弱いクラス」のようなものと思っていただければよいだろう。

 連載最終回となる今回は、JavaScriptで本格的なプログラミングを行うのに欠かせない――そして、JavaScriptに対する苦手意識の根幹でもある(と思われる)「プロトタイプ・ベースのオブジェクト指向」構文について解説する。

 なお、本論に入るに当たって、1点のみ注意していただきたい点がある。第1回でも述べたように、JavaScriptでは厳密な意味でのクラスという概念は存在しない。しかし、本稿を読まれている読者の多くはクラス・ベースのオブジェクト指向構文になじんでいると思われることから、ここではJavaScriptにおいて「クラス的な役割を持つ存在」を、便宜上、「クラス」と呼ぶものとする。ご了承いただきたい。

JavaScriptにおける“クラス”の定義

 JavaScriptにおけるオブジェクト指向構文について理解するには、抽象的な解説を重ねるよりも、まずは具体的なクラスを実際に作成してみた方が話が早いだろう。以下は、JavaScriptで中身を持たない、最も単純なクラスを定義した例だ。

var Animal = function() {};
リスト1 JavaScriptにおける最もシンプルなクラスの例

 変数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); // 「トクジロウ:オス」
リスト2 Animalクラスにname/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(); // 「トクジロウ オス」
リスト3 Animalクラスに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(); // 「トクジロウ オス」
リスト4 インスタンスに対してメソッドを追加した例

 先ほど同様、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(); // エラー発生
リスト5 Animalクラスに対してインスタンスを複数生成した例

 インスタンスanim2からtoStringメソッドを呼び出そうとした時点で、エラーが発生することが確認できる。Internet Explorerでは、恐らく「オブジェクトでサポートされていないプロパティまたはメソッドです。」というエラーメッセージが出力されるはずだ。

 このことから、インスタンスへのメソッドの追加はあくまでインスタンス(ここではanim)への追加であって、「大本であるクラスへの追加ではない」ことが理解できる。つまり、クラス・ベースのオブジェクト指向言語においては、あるクラスを基に作成されたインスタンスは必ず同じメンバを持つはずであるが、JavaScriptでは同一の“クラス”を基に作成されたインスタンスでも異なるメンバを持つ可能性がある、ということなのである。これが、先ほどプロトタイプ・ベースのオブジェクト指向が、クラス・ベースのそれよりも「縛りが弱い」と述べた理由でもある*1

*1 ちなみに、ここではメンバを追加する例について述べたが、インスタンス、あるいはコンストラクタ経由で追加したメンバはdelete演算子(後述)で削除することも可能だ。

 いずれにせよ、ここでは、

インスタンスに追加したメンバは、
(オブジェクト共通ではなく)そのインスタンスのみで有効である

という点を押さえておこう。


 INDEX
  Ajax時代のJavaScriptプログラミング再入門
  第4回 JavaScriptでオブジェクト指向プログラミング
  1.JavaScriptにおける“クラス”の定義
    2.プロトタイプ・ベースのオブジェクト指向
    3.プロトタイプ・チェーン − JavaScriptの継承機構 −
    4.[コラム]オブジェクトと連想配列
 
インデックス・ページヘ  「Ajax時代のJavaScriptプログラミング再入門」

TechTargetジャパン

Insider.NET フォーラム 新着記事
  • Kinectが切り開く“夢の近未来” (2012/2/2)
     日本を含めた世界中でKinect for Windowsセンサー商用版とSDK正式版がリリース。未来のコンピューティングはどう変化するのか?
  • 3つの視点でネイティブと.NETの適材適所を考察 (2012/1/31)
     アプリ開発は「ネイティブ」と「.NET」、どちらが最良? その問いには「適材適所」と答えるしかない。では、“適所”は一体どこかを考察する
  • SQL Azure Data Sync入門 (2012/1/30)
     SQL Azure/SQL Serverデータベース間のデータ同期を簡単に実現するサービスとは? その仕組みや使用手順を解説
  • Windows Phoneアプリ市場の現状を分析する (2012/1/27)
     Windows Phone のアプリ・ストアに日々登録されている多種多様なアプリ。カテゴリ別のアプリ数は? 市場の現状を明らかにする

@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

RSSフィード

キャリアアップ

- PR -
@IT Sepcial

イベントカレンダー

PickUpイベント

- PR -
もっと見る
- PR -

お勧め求人情報

ホワイトペーパーTechTargetジャパン

@IT Sepcial
ソリューションFLASH