やっと理解できた!JSオブジェクト指向プログラミング再入門

既に多くの方が JavaScript のオブジェクト指向的側面についての解説を記事にされていますが、読み手側から見ると、例えばプログラミング言語への習熟度やオブジェクト指向自体に対する理解度がマチマチなわけで、私自身、「おお、なるほど!」 っていう、頭の中のスイッチがパチンッ!と入るような境地には達していませんでした。

かつて私も オブジェクト指向なJavaScriptプログラミングのススメ なんていう翻訳記事を書いてはいるのですが、正直なところ prototype.constructor の存在は知りませんでしたし、Function.callFunction.apply をどう使えばよいのかなどをちゃんと理解できてはいませんでした。

そんな中、2011年12月に書かれた Doc Center | Mozilla Developer Network の記事 オブジェクト指向 JavaScript 入門 は、オブジェクト指向の概念と JavaScript のプログラミング言語仕様をほどよくバランスさせながらも、「JavaScript のオブジェクト指向プログラミングはこうだ!」っていう大胆な書きっぷりが、私的にはスイッチが入ったのでした。

もちろんこの記事に目を留めてもらった人と私のバックグランド (仕事上は組み込み系の C → UML → C++ です) は違いますし、また優れた解説記事が多数ある中、私がこの記事を共有する価値があるのかどうか分かりません。が、私のように クラス に馴染んだ人間に教える良い方法でもあると思ったので、翻訳を中心に、その 感じ を伝えるチャレンジを再度してみたいと思います 😉 。

オブジェクト指向 JavaScript 入門

JavaScript には強力なオブジェクト指向プログラミング能力が備わっていますが、他の言語と比べたときの違いによって、今まで様々な議論がなされてきました。

本稿では、まずオブジェクト指向プログラミングの入門的な解説から入り、次いで JavaScript のオブジェクトモデルを紹介します。そして最後に JavaScript におけるオブジェクト指向プログラミングの概念を、例を用いて示したいと思います。

JavaScriptの復習

もしあなたが、例えば変数や型、関数やスコープといった JavaScript の概念についての理解をより確かなものにしたいなら、JavaScript 再入門 を読むと良いでしょう。また Core JavaScript 1.5 ガイド も参考になります。

オブジェクト指向プログラミング

オブジェクト指向プログラミングは、実世界に基づくモデルを構築する際に、抽象化という技法を用いるプログラミングのパラダイムです。そのために、既に確立されたパラダイムであるモジュール化やポリモーフィズム、カプセル化といった幾つかの技法を用います。今日、広く普及している多くのプログラミング言語 (Java、JavaScript、C#、C++、Python、PHP、Ruby、Objective-C など) がオブジェクト指向プログラミング (OOP) をサポートしています。

古典的なプログラムが、関数の集合、あるいは単なるコンピューターへの命令リストに過ぎないのとは対照的に、オブジェクト指向プログラミングは、オブジェクトの集合を用いるソフトウェア・デザイン手法と見なすことができます。OOP ではそれぞれのオブジェクトが、メッセージを受信し、データを処理し、他のオブジェクトにメッセージを送信することができます。どのオブジェクトも、異なる役割や責務を持って振る舞う独立した小さなマシンと見なすことができるのです。

オブジェクト指向プログラミングは、プログラミングにおける柔軟性や保守性を向上させることを意図していて、実際、大規模なソフトウェア・エンジニアリングの領域において広く普及しています。またモジュール化に強く重点が置かれているため、オブジェクト指向のコードはモジュラ・プログラミング手法 (訳者注:単一目的の小さなモジュールを単位とするプログラミング手法。OS などの構築手法としてよく言及されている) に比べ、開発がよりシンプルで、後々の理解がし易く、またより複雑な状況や手順を分析し、コーディングすることに適しているのです。

用語の解説

クラス

オブジェクトの特性を定義したものです。

オブジェクト

クラスのインスタンス(実体)です。

プロパティ

例えば といった、オブジェクトの特性です。

メソッド

例えば 歩く といった、オブジェクトが持つ機能です。

コンストラクタ

オブジェクトが生成されるときに呼び出されるメソッドです。

継承(インヘリタンス)

あるクラスが別のクラスから特性を継承することができるという性質のことです。

カプセル化

クラスにはオブジェクトの特性だけを定義し、メソッドにはそのメソッドをどのように実行するかだけを定義します (訳者注:一般的なカプセル化の概念としてこの表現には違和感を感じますが、特に JavaScript の OOP の場合、プロパティはコンストラクタで、メソッドはプロトタイプで定義するということを指しているのではないかと思います。もちろんコンストラクタ中でもメソッドは定義できますが、オブジェクトのインスタンス毎に定義されるためメモリ効率が良くありません。一方コンストラクタ中で定義すれば、後述するように各インスタンスはただ1つの関数を参照するようになり、また必要であればインスタンスごとにオーバーライドすることもでき、OOP 言語として自然な感じになります)。

抽象化

オブジェクトの継承やメソッド、プロパティなどの組み合わせを駆使し、問題領域のモデルをシミュレートできるようにすることです。

ポリモーフィズム

複数の異なるクラスに属するメソッドまたはプロパティを定義することができるという性質のことです。

さらに詳しいオブジェクト指向プログラミングの定義については、Wikipedia の オブジェクト指向 をご覧ください。

プロトタイプ・ベースのプログラミング

プロトタイプ・ベースのプログラミング は、オブジェクト指向プログラミングの中でも、クラスを持たないスタイルをとります。オブジェクトの振る舞いの再利用 (クラス・ベースのプログラミング言語の継承に当たる) は、既に存在するオブジェクトをプロトタイプとして流用する形で行われます。このようなプログラミング・モデルは、クラスレスプロトタイプ指向 あるいは インスタンス・ベース のプログラミングとして知られています。

プロトタイプ・ベース言語 の原型は、David Ungar と Randall Smith によって開発された Self というプログラミング言語に見ることができます。しかしながら近年、クラスレスなプログラミング・スタイルが急激に広まりつつあります。例えば JavaScript、Cecil、NewtonScript、lo、MOO、REBOL、Kevo、Squeak (訳者注:スクイーク は、学校教育でも取り入れられているメディア・オーサリング・ツールです) といったものがその代表ですが、他にも多数 存在します。

JavaScriptオブジェクト指向プログラミング

コア・オブジェクト

JavaScript はそのコアに、幾つかのオブジェクトを内包しています。例えば、MathObjectArrayString といったものがあります。次の例は、Math オブジェクトの ramdom() というメソッドの使用例です。

alert(Math.random());
注: 本例以降で出てくる alert という名前の関数は、グローバルに定義されていると仮定します。実際には alert 関数は、JavaScript の一部ではなく、ブラウザに含まれる機能の1つです。

JavaScript に含まれるコア・オブジェクトのリストは、Core JavaScript 1.5 Reference: Global Objects を参照してください。

JavaScript では、どのオブジェクトも Object のインスタンスの1つで、そのプロパティとメソッドを継承しています。

カスタム・オブジェクト

(訳者注:定義済みのコア・オブジェクトに対し、後から定義するオブジェクトを指しています。)

クラス

JavaScript は C++ や Java と異なり、クラス宣言を持たないプロトタイプ・ペースの言語です。これは、クラス宣言を持つ言語に慣れ親しんだプログラマにとっては混乱の元かも知れません。代わりに JavaScript では 関数クラス のごとく使います。クラスの定義は、関数の定義同様に簡単です。以下の例は、Person という新しいクラスを定義していることになります。

function Person() {}

(訳者注:「function をクラス宣言と思え!」という大胆な書きっぷりが、私が気に入った最大の理由です 😉 。これ以降、用語も含めて クラス・ベース の文脈で解説されています。)

オブジェクト(クラスのインスタンス)

オブジェクト obj の新しいインスタンスを作るためには、obj という型を後で参照できるよう new 宣言子を用いて変数に割り当てます。

次の例は、Person という名前のクラスを定義し、person1person2 という2つのインスタンスを作成しています。

function Person() { }
var person1 = new Person();
var person2 = new Person();
インスタンスを作成する新しいメソッド Object.create (訳者注:JavaScript 1.8.5) も参照すると良いでしょう。
コンストラクタ

コンストラクタは、オブジェクトの実体を生成するときに呼び出されます。コンストラクタはクラスのメソッドです。JavaScript では、関数がオブジェクトのコンストラクタとして機能するので、明示的にコンストラクタ・メソッドを定義する必要がありません。クラス内で定義されたどんなアクションも、インスタンス化のたびに実行されるのです。

コンストラクタは、オブジェクトのプロパティを設定したり、オブジェクトを準備するメソッドを呼び出すために使われます。クラスにメソッドを追加/定義する別な方法は、後ほど説明します。

次の例は、クラス Person のインスタンス生成時に、アラートを出すコンストラクタを定義しています。

function Person() {
	alert('Person instantiated');
}

var person1 = new Person();
var person2 = new Person();
プロパティ(オブジェクトの属性)

プロパティは、クラスに内包される変数です。どのオブジェクトのインスタンスもプロパティを持っています。また継承を正しく行うためには、プロパティはクラス (関数) の prototype プロパティの中で設定される必要があります。

クラスの内部でプロパティを機能させるためには、現在のオブジェクトを参照する this というキーワードを使います。クラスの外部からプロパティへ read/write アクセスするには、InstanceName.Property というシンタックスを使います。これは C++ や Java、その他多数の言語と同じ方法です (クラスの内部でプロパティの値を set/get するには、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 プロパティに function とい名前のプロパティを割り当てます。即ち関数に付けられた名前が、そのオブジェクトで呼び出すことのできるメソッドの名前になります。

次の例は、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(); // 'Male'
genderTeller();      // undefined
alert(genderTeller === person1.sayGender);          // true
alert(genderTeller === Person.prototype.sayGender); // true

上記例では、同時に幾つもの概念を表しています。その1つは、JavaScript には オブジェクトごとのメソッド というのはなく、メソッドへの全ての参照は、prototype で定義された、ただ1つの関数を指しているという点です。そしてもう1つは、関数がオブジェクトのメソッド (正確にはプロパティです) として呼び出されたとき、その時の オブジェクトのコンテキストthis という特別な変数に バインド されるという点です。これは次のように関数オブジェクト (この場合 genderTeller) の call メソッドを通じて (この場合は person1 の) this を提供すること同じです。

genderTeller.call(person1); // alerts 'Male'
より詳しくは、Function.callFunction.apply を参照してください。

(訳者注:実際に試した方が良く理解できると思い、少しアレンジした Fiddle を作ってみました。call メソッドのメリットは、次の継承でより明らかになります。)

継承(インヘリタンス)

継承は、1つ、あるいは複数クラスの派生バージョンのクラスを作る機構です (JavaScript の場合、単一クラスの継承だけがサポートされています)。派生クラスは一般に と呼ばれ、その他のクラスは一般に と呼ばれます。JavaScript では、親クラスのインスタンスを子クラスに割り当てることで実現させます。その後、派生部分を定義します。

JavaScript では、子クラスの prototype.constructor プロパティを認識しないので、明示的に宣言しなければなりません。詳しくは Core JavaScript 1.5 Reference: Global Objects: Object: prototype を見てください。

次の例は、Person クラスの子クラスとして Student クラスを定義します。そして sayHello() メソッドを再定義 (オーバーライド) し、さらに sayGoodBye() メソッドを追加します。

(訳者注:Student のコンストラクタで Person のコンストラクタを呼び出す際、コンテキストを Student とするために call を用いています。このようにすれば、継承したメソッドをオブジェクトのインスタンスに合わせて呼び出すことができます。)

カプセル化

前述の例題では、Student クラスは Person クラスの walk() メソッドがどのように実装されているかを知らなくても、そのメソッドを使うことができました。即ち Student クラスは、そのメソッドを変えようと思わない限り、明示的に定義する必要がないということです。これはカプセル化と呼ばれており、どのクラスも親のメソッドを継承することができ、変えたいと望むときだけ定義すれば良いのです。

抽象化

抽象化とは、問題領域をモデリングするためのメカニズムのことです。このモデリングは、継承とコンポジション (集約=あるオブジェクトの中に別なオブジェクトが構成要素として入る関係) によって成されます。JavaScript では継承によって派生が作られ、またクラスのインスタンスが他のオブジェクトの属性値となることでコンポジションが構成されます。

JavaScript の Function クラスは、Object クラスを継承しています (これはモデルの派生です)。そして Function.prototype プロパティは Object のインスタンスととなっています (この関係は、コンポジションです)。

ポリモーフィズム

全てのメソッドとプロパティは prototype プロパティ内で定義されるため、異なるクラスで同じ名前のメソッドを定義することができます。つまり、メソッドはそれが定義されているクラスだけにスコープされているということです。これは、2つのクラスが親と子の関係 (継承チェーンにおいて、一方が他方を継承しない関係にあるとき) にない場合にのみ、真となります。

備考

本稿に記したオブジェクト指向プログラミングを実装するための技法は、JavaScript でだけ使われるものではありません。即ち、オブジェクト指向プログラミングがどのように実行されるかという点に関して言えば、他の言語にも適応させることができます。

同様に、ここに示した技法はどんな言語のハックを使っているわけでもありませんし、他の言語のオブジェクト理論の実装を模倣したわけでもありません。

もちろんここで述べた以外にも、さらに高度な JavaScript のオブジェクト指向プログラミング技法がありますが、本稿のスコープ範囲外となりますので、ここまでにしたいと思います。

参考情報

(訳者注:MDN には、JavaScript に関する日本語リソース が多数ありますし、今回紹介した記事もいずれは日本語に翻訳されると期待しています。本記事の最後は、さらに理解を深めるために参照すると良いと思う MDN のリソースを順に紹介します。)

参照元の記事に対する著作権およびライセンス表記

  • Author(s): Fernando Trasvina <f_trasvina at hotmail dot com>
  • Copyright Information: © 1998-2005 by individual mozilla.org contributors; content available under a Creative Commons license

訳者あとがき

今回紹介した記事は、一見何の変哲もない「JavaScript オブジェクト指向プログラミング入門」ですが、その実、少なくとも私には目からウロコの記事でした。なぜなら今までは、プロトタイプ・ベースとクラス・ベースの違いを意識し過ぎていたからです。そういった目先の違いを強調するのではなく、「大して違いはないさ、ほうらネ、こう思えばいいんだょ」 と、この記事は訴えているのだと思います。

もちろん元記事にもある通り、言語実装上の違いが色々語られているのも事実です。なぜならさらに高度に使いこなすには、その違いをちゃんと意識しなければならないからでしょう。

しかしオブジェクト指向という上位概念は共通なわけで、大雑把でもいいから一旦自分の中に取り込んだ後、今度はその違いに目を向けるというハードルの超え方もアリだと思いますが、いかがでしょうか?

オススメ追記

次の2つのリソースがものすごくオススメです。

やっと理解できた!JSオブジェクト指向プログラミング再入門」への7件のフィードバック

  1. 阿部翔太 by e-publisher

    javascriptは何度か挫折しましたが分かりやすくて助かりました!

    返信
  2. tokkonoPapa

    @阿部翔太 by e-publisher さん、
    ご訪問&コメントありがとうございます。

    多くの方が指摘してくれたように、JavaScript のオブジェクト指向プログラミングの理解の仕方としては正確ではないと思いますが、ただ1つ言えることは、私自身今まで読んでもチンプンカンプンだった JS OOP の解説が、少しずつ理解できるようになってきたということです。

    阿部さんにも、そんなきっかけになってもらえれば幸いです 🙂 。

    返信

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です

海外からの投稿は受け付けていません