OOJS(short for Object-oriented JavaScript)ライブラリが提供する各種機能のうち、本項では”親”クラスからの機能を継承する”子供”のオブジェクトクラス(コンストラクター)の生成方法について解説します。なお、JavascriptのコンストラクターはJavaのコンストラクターとは内容を全く異にするものであることに留意する必要があります。

 

基本的なコンピューターへの知識および利用能力、HTML と CSS への基本的な理解、JavaScript の基本(第一歩構成要素を参照)と OOJS の基本(オブジェクト入門)に慣れている。

目的:

JavaScript でどのように継承ができるようになっているかを理解していること。

プロトタイプでの継承

ここまで動作している継承を見てきました ー プロトタイプチェーンがどのように動作するか、どのようにメンバーが繋がるチェーンから継承されるのかを。しかし、これらの大半はブラウザーに組み込まれた関数に含まれています。他のオブジェクトから継承された JavaScript のオブジェクトをどのように生成するのでしょうか。

具体的な例を使ってどのようの継承が行われているかを見てゆきましょう。

さあ始めてみよう

まず、oojs-class-inheritance-start.html ファイルをローカルにコピーしましょう(あるいはライブ版の実行でも確認できます)。ここでこのモジュールで幅広く使用されてきた Person() というコンストラクターの例を見つけることができます。わずかな違いがあって、コンストラクター内部にプロパティのみが定義されています。

function Person(first, last, age, gender, interests) {
  this.name = {
    first,
    last
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
};

メソッドはすべてコンストラクターのプロトタイプとして定義されています。例えば、

Person.prototype.greeting = function() {
  alert('Hi! I\'m ' + this.name.first + '.');
};

注意: ソースコードに、bio() と farewell() が定義されています。後ほどこれらのメソッドがどのようにほかのコンストラクターで継承されるのかを確認します。

Teacher クラスを作成したい場合を考えましょう。これは最初のオブジェクト指向の特徴にて述べたもののようなクラスで、Person からすべてのメンバーを継承しますが、次のものも内包しています。

  1. 新しいプロパティの subject — これはその先生の教える科目を格納します。
  2. 上書きされた greeting() メソッド、標準の greeting() メソッドよりわずかに固く感じられる — 学校で生徒に語りかける先生により相応しい。

 Teacher() コンストラクターの機能を定義しよう

われわれのまずすべき事は Teacher() コンストラクターを作成する事です — 以下に続くコードを既存コードの下に追加してください。

function Teacher(first, last, age, gender, interests, subject) {
  Person.call(this, first, last, age, gender, interests);

  this.subject = subject;
}

これは多くの点で Person コンストラクターと似ていますが、これまでに見てきたものと異なったものがあります—  call() 関数です。この関数は基本的にその他の場所 (ただし現在のコンテキスト) で定義された関数から呼ぶことができます。最初の引数は関数を実行するときに使用することのできる this の値を表します、また他の引数は実行される関数に渡されるべき値です。

Teacher() コンストラクターは継承元の Person() コンストラクターと同じ引数を取りたいため、 call() を呼び出して、すべての引き数を引数として渡します。

コンストラクターの最後の行は、先生が行うべきであり、一般の人が持たない新たな subject(授業) のプロパティを定義しています。

注意として、下記のソースのように、このようにシンプルにも書けます。

function Teacher(first, last, age, gender, interests, subject) {
  this.name = {
    first,
    last
  };
  this.age = age;
  this.gender = gender;
  this.interests = interests;
  this.subject = subject;
}

しかしながらこれはただ改めてプロパティを再定義しているだけで、 Person() から継承していません、そのため、説明しようとしたポイントが伝わりません。またコード行数が多くもなります。

引数なしのコンストラクターからの継承

もし継承したコンストラクターがパラメータからプロパティの値を取得しない場合、 call() の呼び出しで追加の引数を指定する必要がないことを示しておきます。そのため、例えば、このような本当にシンプルなものがある場合、

function Brick() {
  this.width = 10;
  this.height = 20;
}

このように書くことで width と height プロパティを継承することができます(もちろん、下に挙げる数行のステップの様にすることもできます)。

function BlueGlassBrick() {
  Brick.call(this);

  this.opacity = 0.5;
  this.color = 'blue';
}

 call() の中に this だけを記載していることに注意して下さい— 引数を介して親より設定されるどのプロパティも継承しないので他の引数は不要です。

Teacher()のプロトタイプ とコンストラクターの参照への設定方法

今まではすべて順調でしたが、1点問題があります。新しいコンストラクターを定義して、その中に 1 つの prototype プロパティを持たせ、これはデフォルトでただ自身のコンストラクター関数への参照を保持しています。Person のコンストラクターの prototype プロパティへのメソッド群は持っていません。このことを見てみたいのならば Object.getOwnPropertyNames(Teacher.prototype)  をテキスト入力フィールドや JavaScript コンソールへ入力を試してみてください。そして再度入力する時には、Teacher を Person で置き換えてみてください。新しいコンストラクターもそれらのメソッドを継承していません。このことを確認するために、  Person.prototype.greeting と Teacher.prototype.greeting の出力結果を比較してみてください。 Person()  のプロトタイプに定義されたメソッドを継承するために Teacher() を生成する必要があります。ではどのようにすればよいのでしょうか。

  1. 前回追加した部分の下に以下の行を追加してみましょう:
    Teacher.prototype = Object.create(Person.prototype);
    ここで我々に馴染み深い create() に再度助けてもらいましょう。この場合に新しいオブジェクトを作ってそれを Teacher.prototype の値とするのに使います。新しいオブジェクトは Person.prototype を自身のプロトタイプとして保持し、それがゆえに(必要となる時には) Person.prototype 上で利用できるすべてのメソッドを継承します。
  2. 引き続き理解していく前にもう 1 点必要なことがあります。最後の行に追加した後、Teacher.prototype の constructor プロパティは Person() と同値です、なぜならば、Teacher.prototype に Person.prototype からそれ自身のプロパティを継承しているオブジェクトへの参照を設定しているためです。コードを保存してから、ブラウザーでページをロードし、コンソールに Teacher.prototype.constructor を入力して検証してみましょう。
  3. これは問題になるかもしれません、なので以下の内容をすぐに設定しましょう。 ソースコードにまた戻って最後に以下の行を追加しましょう。
    Teacher.prototype.constructor = Teacher;
  4. ソースコードを保存およびページの再読み込みを行って、 Teacher.prototype.constructor と入力したならば Teacher() と返すでしょう、希望した通りに Person() から継承することができました!

 Teacher() に greeting() ファンクションを付け加える

コードを完成させる前に、Teacher() コンストラクターに新たに greeting() 関数を追加する必要があります。

このようにする一番簡単な方法は Teacher() のプロトタイプに定義することです — コードの最後に以下のコードを追加します。

Teacher.prototype.greeting = function() {
  var prefix;

  if (this.gender === 'male' || this.gender === 'Male' || this.gender === 'm' || this.gender === 'M') {
    prefix = 'Mr.';
  } else if (this.gender === 'female' || this.gender === 'Female' || this.gender === 'f' || this.gender === 'F') {
    prefix = 'Mrs.';
  } else {
    prefix = 'Mx.';
  }

  alert('Hello. My name is ' + prefix + ' ' + this.name.last + ', and I teach ' + this.subject + '.');
};

このコードは教師の挨拶をアラート表示します、IF文で作り出した、性別を基にした適切な敬称を使って。

例を試してみよう

これまでのコードをすべて入力し終えているなら、ソースコード(もしくはあなたの用意した同じようなコードに)の最後に続けて Teacher() からオブジェクトインスタンスを生成してみましょう。

var teacher1 = new Teacher('Dave', 'Griffiths', 31, 'male', ['football', 'cookery'], 'mathematics');

保存し、再読み込みをしたなら、新たな teacher1 オブジェクトのプロパティとメソッドにアクセスしてみましょう、例えば。

teacher1.name.first;
teacher1.interests[0];
teacher1.bio();
teacher1.subject;
teacher1.greeting();
teacher1.farewell();

これらはすべてうまく動作するでしょう。1、2、3、6行目の問い合わせは Person() コンストラクター(class)から継承されたメンバーにアクセスします。4行目の問い合わせはさらに限定された Teacher() コンストラクター(class)でのみ利用可能なメンバーにアクセスします。5行目の問い合わせは Person() から継承されたメンバーにアクセスしようとしますが、Teacher() が同じ名前の自身のメンバーを持ち、そのため問合せはそのメンバーにアクセスすることが例外になります。

注記: もしここまでの例がうまく動作しないなら、あなたのコードと完成版ライブ版も参照)を比較してみてください。

ここで述べている手法は JavaScript でクラスを継承する唯一の方法ではなく、問題なく動作し、JavaScript でのどのように実装するかの良いアイデアを提示しています。

また JavaScript でより明瞭に継承を行えるようにした新しいECMAScriptの機能(Classes を参照)にも興味を持つかもしれません。ここではそれらについて言及はしませんでした、それはまだブラウザー間で幅広くサポートされていないためです。一連の記事で検討してきた他のコード構造はすべて、IE9 やそれ以前のバージョンといった、はるか以前よりサポートされており、それより早くからのサポートを確認する方法となります。

一般的な方法は JavaScript ライブラリを使用することです — よく知られた選択肢のうちの大部分は、よりたやすく素早く利用できる簡易な機能セットを持っています。例えば CoffeeScript は class, extends などを提供します。

追加の特訓

OOP theory セクションにおいて、概念として Student クラスを含めていて、これは Person のすべての特徴を継承し、Teacher の挨拶よりもずっとくだけた Person と異なる greeting() メソッドを保持していました。そのセクションで生徒の挨拶がどのようになっているのかを見てみましょう、そして Person() のすべての特徴を継承し、異なる greeting() 関数を実装した、自身の Student() コンストラクターを実装してみましょう。

注記: もしここまでの例がうまく動作しないなら、あなたのコードと完成版動作するライブ版も参照)を比較してみてください。

Object メンバーの概要

要約すれば、基本的にはプロパティ/メソッドについて 3 種類の気にする点があります。

  1. Object メンバーはオブジェクトインスタンスへ渡されるコンストラクター関数の内部に定義されます。区別は比較的簡単です — あなたの用意したコードでは、this.x = x という型の行でコンストラクターの内部に定義されたメンバーがあります。ブラウザーの組み込みコードでは、オブジェクトインスタンスからのみ利用可能なメンバーが存在します(通例、new キーワードでコンストラクターを呼び出すことで生成され、例えば var myInstance = new myConstructor() )。
  2.  Object メンバーがコンストラクター自身に直接定義されている場合、コンストラクターでのみ利用することができます。これらは通常組み込みブラウザーオブジェクトでのみ利用ができます、インスタンスではなくコンストラクターに対して、直接連携することで認識されます。例えば、 Object.keys() 。
  3. Object メンバーはコンストラクターのプロトタイプに定義されていて、すべてのインスタンスと継承しているオブジェクトのクラスによって継承されます。これらはコンストラクターのプロトタイプで定義されているあらゆるメンバーを含みます、例えば、 myConstructor.prototype.x() .

もしどれがどれを指すかを区別できないのであれば、まだ気にしないでください — あなたはまだ学習中で、実践を通じて精通することでしょう。

JavaScript でいつ継承を使用するのでしょうか?

特にこの最後の記事を読み終えた後、「うーん、これはややこしいな。」と考えることでしょう。ええ、それは正しい感想です。プロトタイプと継承は JavaScript のもっとも複雑な面のいくつかに当たります、しかし多くの JavaScript の能力と柔軟性はそのオブジェクトの構造と継承に由来します、そしてそれがどのように動作するかは理解するに値します。

ある意味では、常に継承を使用しています。Web API の様々な機能、文字列や配列といったブラウザーに組み込まれたオブジェクトで定義されているメソッド/プロパティを使用するときはいつも、暗黙の内に継承を使用しています。

コードに継承を使用していることに関して、特に開始時には、そして小さなプロジェクトでは多分頻繁には使っていないでしょう。不要にも関わらず、継承のためだけにオブジェクトおよび継承を使用するのは時間の浪費です。しかしコードの母体が大きくなればなるほど、継承についての必要性が目に付きやすくなってきます。同じような機能を持ついくつものオブジェクトを作成していることに気付いた場合は、共有機能を持つ汎化オブジェクトタイプを作成し、特化オブジェクトタイプでそれらの機能を継承させるのがお手軽であり、便利です。

注記: プロトタイプチェーンなどを使って JavaScript が動作する方法のために、オブジェクト間での機能の共有をしばしば 移譲 と呼ぶ事があります。特化オブジェクトは汎化オブジェクトタイプから機能的に移譲されています。

継承を使用している時、継承をやたら多いレベルに行わないように、メソッドとプロパティをどこに定義したかを注意深く追跡し続けるようにアドバイスされるでしょう。組み込みブラウザーのオブジェクトのプロトタイプを一時的に変更するコードを書き始めることは可能ですが、実際に良い理由がないのであれば、そうすべきではありません。過剰な継承は終わりない混乱や、そんなコードをデバックする時は終わりない痛みに繋がりかねません。

究極的には、オブジェクトは関数やループのような、自身の固有の役割や長所を活かした、コードの再利用の単なる別の形でもあります。もし関連のある変数や関数の一団を作成していることに気付き、それらすべてを追跡して適切にパッケージ化したいのであれば、オブジェクトは良いアイデアです。オブジェクトはまた、ある所から別の所にデータの集合を渡したい場合にも大変便利です。これらの事柄の両方がコンストラクターや継承を使用する事なく達成できます。もしオブジェクトの単一のインスタンスが必要なだけであれば、オブジェクトリテラルを使用するのが多分より良く、確実に継承は必要ないでしょう。

要約

この記事は今知っておくべき考えられる OOJS の核となる理論および文法の残りの部分をカバーしています。この時点で、 JavaScript オブジェクトおよび オブジェクト指向プログラミングの基本、プロトタイプとプロトタイプにおける継承、クラス(コンストラクター)とオブジェクトのインスタンスの生成、クラスへの機能の追加、他のクラスから継承されたサブクラスの生成をどのように行うか、を理解しているでしょう。

次の記事では JavaScript Object Notaion (JSON) 、つまり  JavaScript オブジェクトを使用して書かれた共通データ交換フォーマット、がどのように動作するかをを見て行きましょう。

参照

  • ObjectPlayground.com — オブジェクトについて学べる非常に有益な会話型学習サイト
  • Secrets of the JavaScript Ninja, Chapter 6 — John Resig Bear Bibeault による、先進的な JavaScript のコンセプトおよび手法についての良書。6 章ではプロトタイプや継承の非常に有効な面が説明されている。多分プリントやオンラインのコピーを比較的簡単に追跡する事ができるでしょう。
  • You Don't Know JS: this & Object Prototypes — Kyle Simpson による JavaScript を説明したすぐれたシリーズの一部。特に 5 章はプロトタイプについて、我々の説明より相当詳細に説明しています。初心者向けのこのシリーズ記事では単純化した見方を提供してきましたが、いっぽう Kyle は非常に深く論じており、より複雑だがより正確な図を提供しています。

 

このモジュールに含まれる

 

ドキュメントのタグと貢献者

このページの貢献者: AkihikoTakeda, Uemmra3, hamasaki, kenji-yamasaki
最終更新者: AkihikoTakeda,