オブジェクト指向 JavaScript と他のオブジェクト指向言語との違いによる論争はあるものの、JavaScript は強力なオブジェクト指向プログラミングの機能を備えています。
この記事ではオブジェクト指向プログラミングの入門から始めて、JavaScript オブジェクトモデルの復習、そして最後に JavaScript のオブジェクト指向プログラミングの概念を説明します。
JavaScript の復習
変数、型、関数、スコープといった JavaScript の概念について自信がないのでしたら、JavaScript 「再」入門で該当するトピックをご覧いただくとよいでしょう。また、JavaScript ガイドもご覧いただくとよいでしょう。
オブジェクト指向プログラミング
オブジェクト指向プログラミングは、実世界を元にしたモデルを作成するために抽象化を使用する、プログラミングのパラダイムです。これはモジュラリティ、ポリモーフィズム、カプセル化といった、以前に確立したパラダイム由来の技術をいくつか使用します。今日、多くの人気があるプログラミング言語 (Java、JavaScript、C#、C++、Python、PHP、Ruby、Objective-C など) がオブジェクト指向プログラミング (OOP) をサポートしています。
プログラムは関数の集まり、あるいは単にコンピュータに対する命令のリストであるという従来の見方とは対照的に、オブジェクト指向プログラミングは協調して動作するオブジェクトの集まりを使用したソフトウェア設計としてみられることがあります。OOP では、各々のオブジェクトがメッセージ受信、データの処理、他のオブジェクトへのメッセージ送信をできます。各々のオブジェクトは明確な役割や責任を持つ、独立した小さな機械であるとみることができます。
オブジェクト指向プログラミングはプログラミングにおける柔軟性や保守性の向上を目指しており、大規模なソフトウェア工学において広く普及しています。モジュラリティを強く重視するという長所により、オブジェクト指向のコードは開発のシンプル化や後から理解することの容易化が目指されて、モジュラリティの低いプログラミング方法よりも直接的な分析、コーディング、複雑な状況や手続きの理解に適しています。2
用語
- クラス (Class)
- オブジェクトの特性を定義するものです。
- オブジェクト (Object)
- クラスの実体です。
- プロパティ (Property)
- "色" のような、オブジェクトの特性です。
- メソッド (Method)
- "歩く" といった、オブジェクトの能力です。
- コンストラクタ (Constructor)
- インスタンス化の瞬間に呼び出されるメソッドです。
- 継承 (Inheritance)
- あるクラスが別のクラスから特性を引き継げることです。
- カプセル化 (Encapsulation)
- クラスはオブジェクトの特性のみを定義し、メソッドは自身をどのように実行するかのみを定義することです。
- 抽象化 (Abstraction)
- オブジェクトの複雑な継承、メソッド、プロパティの集まりが、実世界のモデルを必ず再現できることです。
- ポリモーフィズム (Polymorphism)
- 別々のクラスが同じメソッドやプロパティを定義してもよいことです。
オブジェクト指向プログラミングのより広範な説明については、Wikipedia の Object-oriented programming (日本語版) をご覧ください。
プロトタイプベースのプログラミング
プロトタイプベースのプログラミングは、クラスが存在せず、また動作の再利用 (クラスベースの言語では継承として知られています) はプロトタイプになる既存のオブジェクトを装飾するプロセスによって成される、オブジェクト指向プログラミングのスタイルです。このモデルはクラスレス、プロトタイプ指向、あるいはインスタンスベースのプログラミングとしても知られています。
プロトタイプベース言語の最初の (またもっとも規範的な) 実例は、David Ungar 氏と Randall Smith 氏によって開発された Self プログラミング言語です。とはいえ、クラスレスのプログラミングスタイルは最近ますます人気が高まっており、また JavaScript、Cecil、NewtonScript、Io、MOO、REBOL、Kevo、Squeak (Morphic コンポーネントの操作に Viewer フレームワークを使用するとき) などのプログラミング言語に採用されました。2
JavaScript のオブジェクト指向プログラミング
コアオブジェクト
JavaScript は、コア部にオブジェクトがいくつか含まれています。例えば Math、Object、Array、 String といったオブジェクトです。以下の例では、Math オブジェクトの random()
を使用して乱数を得るための使い方を示しています。
alert(Math.random());
alert
という名前の関数 (Web ブラウザが内蔵している関数など) がグローバルで定義されていると仮定します。実際は、alert
関数は JavaScript そのものの一部ではありません。JavaScript におけるコアオブジェクトの一覧については、JavaScript リファレンスの Global Objects をご覧ください。
JavaScript ではすべてのオブジェクトが Object
オブジェクトのインスタンスであり、それゆえに Object
の全プロパティおよび全メソッドを継承します。
カスタムオブジェクト
クラス
JavaScript は C++ や Java などでみられる class 文を持たない、プロトタイプベースの言語です。これは時に、class 文を持つ言語に慣れているプログラマを混乱させます。その代わりに、JavaScript ではクラスとして関数を使用します。クラスの定義は、関数の定義と同じほど簡単です。以下の例では、Person という名前の新たなクラスを定義しています。
function Person() { }
オブジェクト (クラスのインスタンス)
obj
オブジェクトの新たなインスタンスを生成するには new obj
文を使用して、その結果 (これは obj
型です) を後からアクセスするための変数に代入します。
以下の例では Person
という名前のクラスを定義して、2 つのインスタンス (person1
と person2
) を生成しています。
function Person() { } var person1 = new Person(); var person2 = new Person();
Object.create
もご覧ください。コンストラクタ
コンストラクタは、インスタンス化の瞬間 (オブジェクトのインスタンスが生成される瞬間) に呼び出されます。コンストラクタは、クラスのメソッドです。JavaScript では、関数がオブジェクトのコンストラクタになります。従って、コンストラクタメソッドを明示的に定義する必要はありません。クラス内で定義されたすべてのアクションが、インスタンス化の際に実行されます。
コンストラクタは、オブジェクトのプロパティの設定やオブジェクトの使用準備を行うメソッドの呼び出しを行うために使用されます。クラスのメソッドの追加や、メソッドの定義は別の構文を使用して行うことについては、後ほど説明します。
以下の例では、Person
クラスをインスタンス化する際にコンストラクタがアラートを表示します。
function Person() { alert('Person instantiated'); } var person1 = new Person(); var person2 = new Person();
プロパティ (オブジェクトの属性)
プロパティはクラス内にある変数です。オブジェクトのインスタンスはすべて、それらのプロパティを持ちます。継承が正しく行われるように、プロパティはクラス (関数) のプロトタイプのプロパティで設定するべきです。
this
キーワードを使用して、クラス内でプロパティを扱います。このキーワードは、カレントオブジェクトを参照します。クラス外からのプロパティへのアクセス (読み取りや書き込み) は、以下の構文で行います: InstanceName.Property
。これは C++、Java、その他の言語と同じ構文です。(クラスの内部で、構文 this.Property
はプロパティの値の取得や設定に使用します)
以下の例では Person
クラスに gender
プロパティを定義して、インスタンス化の際に設定しています。
function Person(gender) { this.gender = gender; alert('Person instantiated'); } var person1 = new Person('Male'); var person2 = new Person('Female'); //display the person1 gender alert('person1 is a ' + person1.gender); // person1 is a Male
メソッド
メソッドはプロパティと同じ考え方に従います。プロパティとの違いは、関数であることと関数として定義されることです。メソッドの呼び出しはプロパティへのアクセスと似ていますが、メソッド名の終わりに ()
を付加して、おそらく引数を伴います。メソッドを定義するには、クラスの prototype
プロパティの名前付きプロパティに関数を代入します。関数を代入する名前は、オブジェクトでメソッドを呼び出す名前になります。
以下の例では、Person
で sayHello()
メソッドを定義および使用しています。
function Person(gender) { this.gender = gender; alert('Person instantiated'); } Person.prototype.sayHello = function() { alert ('hello'); }; var person1 = new Person('Male'); var person2 = new Person('Female'); // call the Person sayHello method. person1.sayHello(); // hello
JavaScript のメソッドは、"コンテキストに関係なく" 呼び出せることを意味するプロパティとしてクラスやオブジェクトに割り付けられた、通常の関数オブジェクトです。以下のコード例について考えてみましょう:
function Person(gender) { this.gender = gender; } Person.prototype.sayGender = function() { alert(this.gender); }; var person1 = new Person('Male'); var genderTeller = person1.sayGender; person1.sayGender(); // alerts 'Male' genderTeller(); // alerts undefined alert(genderTeller === person1.sayGender); // alerts true alert(genderTeller === Person.prototype.sayGender); // alerts true
この例では、一度に多くの概念を示しています。JavaScript ではメソッドへの参照すべてがまったく同じ関数を参照しているため、"オブジェクトごとのメソッド" はありません。その参照先は、プロトタイプではじめに定義した関数です。JavaScript は、関数がオブジェクトのメソッド (あるいは、厳密に言えばプロパティ) として呼び出されるときに、カレントの "オブジェクトのコンテキスト" を、特別な変数 "this" に "割り当て" ます。これは、以下のように function オブジェクトの "call" メソッドを呼び出すのと同じです:
genderTeller.call(person1); //alerts 'Male'
継承
継承は、1 つ以上のクラスを特化したバージョンとしてクラスを作成する方法です (JavaScript は 1 つのクラスの継承のみサポートします). 特化したクラスは一般的に 子 と呼ばれ、また他のクラスは一般的に親と呼ばれます。JavaScript では親クラスのインスタンスを子クラスに代入して、特化させることにより継承を行います。新しいブラウザでは、継承の実装に Object.create
を使用することもできます。
JavaScript は子クラスの prototype.constructor
プロパティ (JavaScript リファレンス:Global Objects:Object:prototype をご覧ください) を検出しませんので、手動で明示しなければなりません。
以下の例では、Person
クラスの子クラスとして Student
クラスを定義しています。そして、sayHello()
メソッドの再定義と sayGoodBye()
メソッドの追加を行っています。
// define the Person Class function Person() {} Person.prototype.walk = function(){ alert ('I am walking!'); }; Person.prototype.sayHello = function(){ alert ('hello'); }; // define the Student class function Student() { // Call the parent constructor Person.call(this); } // inherit Person Student.prototype = new Person(); // correct the constructor pointer because it points to Person Student.prototype.constructor = Student; // replace the sayHello method Student.prototype.sayHello = function(){ alert('hi, I am a student'); } // add sayGoodBye method Student.prototype.sayGoodBye = function(){ alert('goodBye'); } var student1 = new Student(); student1.sayHello(); student1.walk(); student1.sayGoodBye(); // check inheritance alert(student1 instanceof Person); // true alert(student1 instanceof Student); // true
継承を行う際に Object.create
を使用すると以下のようになります:
Student.prototype = Object.create(Person.prototype);
カプセル化
前の例で、Student
は Person
クラスの walk()
メソッドがどのように実装されているかを知る必要がありませんが、そのメソッドを使用できます。Student
クラスはメソッドの変更を望まないのであれば、明示的に定義する必要はありません。これは カプセル化 と呼ばれます。すべてのクラスは親のメソッドを継承して、変更したいメソッドしか定義する必要はありません。
抽象化
抽象化は、取り組んでいる問題の現在の部分をモデル化することを可能にする仕組みです。これは継承 (特化) やコンポジションによって実現します。JavaScript では特化を継承によって、コンポジションをクラスのインスタンスが別のオブジェクトの属性の値になることを可能にすることで実現します。
JavaScript の Function クラスは Object クラスから継承しています (これはモデルの特化を実証します)。また、Function.prototype
プロパティは Object
のインスタンスです (これはコンポジションを実証します)。
var foo = function(){}; alert( 'foo is a Function: ' + (foo instanceof Function) ); alert( 'foo.prototype is an Object: ' + (foo.prototype instanceof Object) );
ポリモーフィズム
すべてのメソッドやプロパティが prototype プロパティの内部で実装されていることと同様に、異なるクラスが同じ名前のメソッドを定義できます。メソッドは、自身が定義されたクラスに収められます。これは、2 つのクラスが親子関係を持たない (継承チェーンにおいて片方がもう一方から継承しない) 場合にのみ成立します。
注記
オブジェクト指向プログラミングを実装するためにこの記事で紹介した技術は、JavaScript のみで使用できるものではなく、どのようにオブジェクト指向プログラミングを実施できるかという観点でとても融通がききます。
同様に、ここで示した技術は言語のハックをまったく使用していませんし、他の言語のオブジェクト理論の実装を模倣してもいません。
他にもより高度な JavaScript のオブジェクト指向プログラミングの技術がありますが、それらはこの入門記事で扱う範囲を超えます。
参考情報
- Mozilla. "JavaScript ガイド", http://developer.mozilla.org/ja/docs/Web/JavaScript/Guide
- Wikipedia. "Object-oriented programming", http://en.wikipedia.org/wiki/Object-...ed_programming
Original Document Information
- Author(s): Fernando Trasviña <f_trasvina at hotmail dot com>
- Copyright Information: © 1998-2005 by individual mozilla.org contributors; content available under a Creative Commons license