この記事は翻訳作業中です。

基礎が片付いたところで、オブジェクト指向 JavaScript (OOJS) について取り上げます。この記事ではオブジェクト指向プログラミング (OOP) の基本的な視点を説明し、JavaScript がどのようにコンストラクター関数を通じてオブジェクトクラスをエミュレートしているか、またどのようにオブジェクトインスタンスを生成しているかを紹介します。

前提条件: 基礎的なコンピューターリテラシー、基礎的な HTML と CSS の理解、JavaScript (JavaScript の第一歩JavaScript の構成要素を参照)及び OOJS (JavaScript オブジェクトの基本を参照)の基礎知識。
到達目標: オブジェクト指向プログラミングの基本理論、どのようにそれが JavaScript (「すべてはオブジェクトである」)に関連しているか、どのようにコンストラクターがオブジェクトインスタンスを生成しているかを理解する。

オブジェクト指向プログラミング - その基本

はじめに、オブジェクト指向プログラミング (OOP) とは何か、シンプルかつ高レベルな視点を提示します。シンプルと述べたのは、OOP はあっという間にひどく複雑になり得るためで、現時点でそのすべてを論じてしまうと、助けとなるよりもむしろ混乱を生んでしまうことでしょう。OOP の基本的な考え方は、プログラムの中で扱いたい、現実世界の事物を模るためにオブジェクトを使用すること、またはそうしなければ使うことが難しいあるいは不可能だった機能にアクセスするための、シンプルな方法を提供することです。

オブジェクトは、モデル化しようとしている事物に関する情報及び、持たせたい機能や動作を表現する、関連したデータとコードを持つことができます。オブジェクトのデータ(しばしば関数も含む)はオブジェクトのパッケージの中(名前空間と呼ばれることがある)に適切に格納されます(カプセル化)。オブジェクトは一般に、ネットワークを通じて容易に送信することが可能な、データストアとしても使われます。

オブジェクトのテンプレートを定義する

学校の生徒と教師の情報を表示する、シンプルなプログラムを考えてみましょう。特定のプログラミング言語の文脈ではなく、OOP 一般の理論を眺めていきます。

はじめに、最初のオブジェクトの記事にある、人物の包括的なデータや機能を定義した、Person オブジェクトに戻りましょう。ある人物について知り得る事柄は数多くあります(住所、身長、靴のサイズ、DNA 情報、パスポート番号、顕著な人格特性など)が、このケースでは名前、年齢、性別、趣味を表示することに興味があるだけです。また、このデータに基づいた短い自己紹介や、挨拶をさせられるようにもしましょう。これは抽象化 — より複雑な事物を、プログラムの目的に沿って簡単に操作できるように、その最も重要な側面を表現する、シンプルなモデルを作ること — として知られています。

いくつかの OOP 言語では、このような包括的なオブジェクト型定義をクラスと呼んでいます(JavaScript は以下に示すように、異なるメカニズムや用語を使っています)。クラスは実際にはオブジェクトではなく、オブジェクトが持つべき特性を定義するテンプレートです。

実際のオブジェクトを生成する

このクラスから、オブジェクトインスタンスを生成することができます。オブジェクトインスタンスは、クラスで定義されたデータや機能を持ったオブジェクトです。Person クラスから、何名かの実際の人物を生成します。

クラスからオブジェクトインスタンスが生成されるとき、クラスのコンストラクター関数が生成のために実行されます。クラスからオブジェクトインスタンスが生成される過程をインスタンス化と呼びます。オブジェクトインスタンスは、クラスをインスタンス化したものです。

専門のクラス

このケースで求めているのは、包括的な人物ではなく、より特定のタイプである、教師と生徒です。OOP では、他のクラスを元にした新しいクラスを作ることができます。これらの新しい子クラスは、親クラスからデータやコード機能を継承することができ、すべてのオブジェクトタイプに共通する機能を、重複させるのではなく、再利用することができます。クラス間で機能が異なる場合は、必要に応じて特殊化された機能を直接定義することができます。

これは実に役立ちます。教師と生徒は名前、性別、年齢のように多数の共通機能を共有しており、これらの機能を一度だけ定義すればいいので便利です。異なるクラスで、同じ機能を分けて定義することもでき、その機能の各定義は異なる名前空間に置かれます。例えば、生徒の挨拶は "Yo, I'm [firstName]"(例:Yo, I'm Sam)という形式とし、一方の教師の挨拶は、より形式的に "Hello, my name is [Prefix] [lastName], and I teach [Subject]." (例:Hello, My name is Mr Griffiths, and I teach Chemistry)のように。

: このような、同じ機能を複数のオブジェクトタイプが実装することを示す用語に、ポリモーフィズムがあります。不思議に感じている場合に備えて。

子クラスのオブジェクトインスタンスを生成しましょう。例:

記事の続きでは、OOP 理論が JavaScript でどのように実践されているかを見ていきます。

コンストラクターとオブジェクトインスタンス

JavaScript は本物のオブジェクト指向言語ではないと主張する人たちがいます。例えば class 文は、既に存在するプロトタイプ式継承の単なるシンタックスシュガーであり、伝統的な class の価値観ではないと。JavaScript は、オブジェクトとその機能を定義するため、コンストラクター関数と呼ばれる特別な関数を使用します。いくつオブジェクトを生成するか知らないという状況によく遭遇するので、これは便利です。コンストラクターは必要な数のオブジェクトを効果的な方法で生成し、必要に応じてデータや関数をそれらに与える手段を提供します。

新しいオブジェクトインスタンスがコンストラクターによって生成されたとき、そのコアとなる機能(プロトタイプとして定義されており、Object prototypes の記事で紹介しています)は「古典的な」 OO 言語のように新しいオブジェクトに対しすべてをコピーされているわけではなく、その代わりの機能は、プロトタイプチェーンと呼ばれる、参照の連鎖を通じて繋がるというものです。 

: 「古典的な OOP」ではないということは、必ずしも悪いことではありません。上述のとおり、OOP はあっという間に複雑になり得ます。JavaScript には、深入りしすぎる必要の無い、オブジェクト指向の機能を巧みに利用するためのいくつかの良い機能があります。

JavaScript でコンストラクターを通じてクラスを作り、そこからオブジェクトインスタンスを生成するところを見ていきましょう。まずは、最初のオブジェクトの記事で見た oojs.html ファイルの新しいコピーを、ローカルに作っておいてください。

シンプルな例

  1. まずは人物を通常の関数で定義するところから見ていきましょう。以下に記述されているコードを script 要素の中に加えてください。
    function createNewPerson(name) {
      var obj = {};
      obj.name = name;
      obj.greeting = function() {
        alert('Hi! I\'m ' + this.name + '.');
      };
      return obj;
    }
  2. この関数を呼び出すことで、新しい人物を生成することができます。次の 3 行をブラウザの JavaScript コンソールで試してみてください。
    var salva = createNewPerson('Salva');
    salva.name;
    salva.greeting();
    これも十分上手くいっていますが、やや長ったらしいです。オブジェクトを生成したいと知っているなら、なぜ明示的に空のオブジェクトを生成し、返すことが必要なのでしょうか?幸いにも、JavaScript はコンストラクター関数という形で、便利なショートカットを提供してくれます。早速作ってみましょう!
  3. 前の関数を、以下のもので置き換えてください。
    function Person(name) {
      this.name = name;
      this.greeting = function() {
        alert('Hi! I\'m ' + this.name + '.');
      };
    }

コンストラクター関数は、JavaScript 版のクラスです。関数の中にすべての期待される機能を持っているが、何も返さない、あるいは明示的にオブジェクトを生成しないということが気になったかもしれません。基本的には、プロパティとメソッドを定義するだけです。加えて、this キーワードが使われていることに気づいたかもしれません。基本、オブジェクトインスタンスの 1 つが作成されるときにはいつでも、オブジェクトの name プロパティはコンストラクター呼び出しに渡される name 値と等しくなり、greeting() メソッドはコンストラクター呼び出しに渡される name 値も使用します。

: 通常、コンストラクター関数の名前は大文字で始まります。コードの中で、コンストラクター関数がより容易に認識されるようにするための慣習です。

では、同じオブジェクトを生成するために、どのようにコンストラクターを呼び出すでしょうか?

  1. 次の 2 行を、前のコードの続きに加えてください。
    var person1 = new Person('Bob');
    var person2 = new Person('Sarah');
  2. コードを保存し、ブラウザをリロードして、以下の 4 行をテキスト入力欄に入れて試してみてください。
    person1.name
    person1.greeting()
    person2.name
    person2.greeting()

素晴らしい!2 つの新しいオブジェクトが、異なる名前空間の下でページに格納されていることが確認できます。それらのプロパティとメソッドにアクセスするときには、person1 または person2 から始めなければなりません。他の機能と衝突してしまわないよう、それらは適切にパッケージされています。しかしながら、それらは同じ name プロパティと greeting() メソッドが利用可能です。2 つのオブジェクトはそれぞれ、生成されたときに割り当てられた、自身の name 値を使っていることに注意してください。これが this を使うことがとても重要である理由の 1 つであり、他の値ではなく、自身の値を使っているのです。

コンストラクターをもう一度呼び出してみましょう。

var person1 = new Person('Bob');
var person2 = new Person('Sarah');

いずれのケースでも、新しいオブジェクトインスタンスを生成したいとブラウザに伝えるために new キーワードが使われており、その後に、括弧に必要なパラメータを入れた関数名が続き、その結果が変数に格納されていて、一般的な関数の呼ばれ方とよく似ています。どちらのインスタンスも、次の定義によって生成されています。

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

新しいオブジェクトが生成された後、person1 及び person2 変数は、次のオブジェクトを格納しています。

{
  name: 'Bob',
  greeting: function() {
    alert('Hi! I\'m ' + this.name + '.');
  }
}

{
  name: 'Sarah',
  greeting: function() {
    alert('Hi! I\'m ' + this.name + '.');
  }
}

コンストラクター関数を呼び出すとき、毎回 greeting() メソッドを定義していることに注意してください。これは理想的ではありません。これを回避するために、代わりにプロトタイプに関数を定義することができます。後で見てみましょう。

最終的なコンストラクターの作成

上で見てきた例は、スタートのためのシンプルな例に過ぎません。次は最終的な Person() コンストラクター関数を作りましょう。

  1. ここまでに挿入したコードを削除し、代わりとなるコンストラクターを追加してください。これはシンプルな例とほぼ同じもので、ほんのわずか複雑になっているだけです。
    function Person(first, last, age, gender, interests) {
      this.name = {
        first,
        last
      };
      this.age = age;
      this.gender = gender;
      this.interests = interests;
      this.bio = function() {
        alert(this.name.first + ' ' + this.name.last + ' is ' + this.age + ' years old. He likes ' + this.interests[0] + ' and ' + this.interests[1] + '.');
      };
      this.greeting = function() {
        alert('Hi! I\'m ' + this.name.first + '.');
      };
    }
  2. ではその下に、コンストラクターからオブジェクトインスタンスを生成するため、次の行を追加してください。
    var person1 = new Person('Bob', 'Smith', 32, 'male', ['music', 'skiing']);

ちょうど最初のオブジェクトを定義したときのように、プロパティやメソッドにアクセスできることを確認できるはずです。

person1['age']
person1.interests[1]
person1.bio()
// etc.

: もしこの工程で何らかのトラブルがあった場合は、あなたのコードを我々のバージョン(oojs-class-finished.htmlライブサンプルも)と比べてみてください。

さらなるエクササイズ

まずはじめに、さらにいくつかのオブジェクトを生成する独自の行を追加し、オブジェクトインスタンスのメンバの取得や設定をしてみてください。

加えて、bio() メソッドにはいくつかの問題点があります。人物が女性である、あるいは他の優先される性別分類の場合でも、その出力には常に "He" という代名詞が含まれています。また、bio は interests 配列により多くのものが列挙されていても、2 つの趣味しか含めません。このクラス定義(コンストラクター)の問題を、あなたはどのように修正することができますか?コンストラクター内に任意のコード(恐らく、いくつかの条件分岐やループが必要となるでしょう)を入れてみてください。性別や、趣味の数が 1、2、あるいは 2 よりも多いかどうかによって、文がどのように構築されるべきか考えてみてください。

: もし詰まってしまった場合は、GitHubに答えとなるリポジトリライブ)があります。最初はあなた自身で書いてみてください!

オブジェクトインスタンスを生成する他の方法

ここまで、オブジェクトインスタンスを生成する 2 つの異なる方法を見てきました。オブジェクトリテラルの宣言と、上で見たコンストラクター関数の使用です。

これらは有意義ですが、他にも方法はあります。Web を巡る際に遭遇したときに備えて、よく知っておいてください。

Object() コンストラクター

まず最初に、Object() コンストラクターを新しいオブジェクトの生成のために使うことができます。はい、一般的なオブジェクトにも、空のオブジェクトを生成するコンストラクターがあります。

  1. このコードを JavaScript コンソールに入力してみましょう。
    var person1 = new Object();
  2. person1 変数に空のオブジェクトが格納されました。このオブジェクトに、ドット記法とブラケット記法を使ってプロパティを追加することができます。次の例を試してみましょう。
    person1.name = 'Chris';
    person1['age'] = 38;
    person1.greeting = function() {
      alert('Hi! I\'m ' + this.name + '.');
    };
  3. あらかじめプロパティやメソッドを設定するため、Object() コンストラクターに引数としてオブジェクトリテラルを渡すことも可能です。次のコードを試してみてください。
    var person1 = new Object({
      name: 'Chris',
      age: 38,
      greeting: function() {
        alert('Hi! I\'m ' + this.name + '.');
      }
    });

create() メソッドの使用

Constructors can help you give your code order—you can create constructors in one place, then create instances as needed, and it is clear where they came from.

However, some people prefer to create object instances without first creating constructors, especially if they are creating only a few instances of an object. JavaScript has a built-in method called create() that allows you to do that. With it, you can create a new object based on any existing object.

  1. Try this in your JavaScript console:
    var person2 = Object.create(person1);
  2. Now try these:
    person2.name
    person2.greeting()

You'll see that person2 has been created based on person1—it has the same properties and method available to it.

One limitation of create() is that IE8 does not support it. So constructors may be more effective if you want to support older browsers.

We'll explore the effects of create() in more detail later on.

おさらい

This article has provided a simplified view of object-oriented theory — this isn't the whole story, but it gives you an idea of what we are dealing with here. In addition, we have started to look at how JavaScript relates to and how it differs from "classic OOP", how to implement classes in JavaScript using constructor functions, and different ways of generating object instances.

In the next article, we'll explore JavaScript object prototypes.

このモジュール内の文書

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

 このページの貢献者: mfuji09, sii
 最終更新者: mfuji09,