DESIGNMAP

  1. TOP
  2. 公開講座
  3. ゼロから始めるJavaScript入門
  4. プロトタイプ継承 ー ゼロから始めるJavaScript入門(ECMAScript 2015)【Vol.14】

プロトタイプ継承 ー ゼロから始めるJavaScript入門(ECMAScript 2015)【Vol.14】

今回はプロトタイプ継承を学びます。JavaScriptの最大の特徴といえるところで、他の言語にはない仕組みです。
このプロトタイプ継承がわかると、大きくJavaScriptの理解が深まります。

筆者が初めてプロトタイプ継承を学んだのが、ActionScript1(ECMAScript準拠のスクリプト)のときです。当初はまったく理解できなかったことを覚えています。

率直に書くとプロトタイプ継承は用語がややこしいだけで、仕組みはシンプルです。解説は最も単純な例からじっくりすすめていきます。

基本の考え方

Atomで新規ファイルをつくり「文字列型 ー ゼロから始めるJavaScript入門(ECMAScript 2015)【Vol.2】」で作った「js-study」フォルダ内に、「prototype1.html」というファイル名で保存します。

以下のコードを書いて保存します。空のオブジェクトをつくっています。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title></title>
  </head>
  <body>
  <script>

  let book = new Object();
  console.log(book);
  
  </script>
  </body>
</html>

Google Chromeで「prototype1.html」を開き、JavaScriptコンソールを開きます。「Object」が出力されます。

横の三角を開くと、__proto__というプロパティが表示されます。その横の三角をさらに開くと、プロパティがみえます。これはObject.prototypeのプロパティです。このように用語がややこしくて混乱します。

let book = new Object();

Object()は組み込みのコンストラクタです。コンストラクタはプロトタイプオブジェクトをもっていて、コンストラクタからつくられたオブジェクトは、プロトタイプオブジェクトのプロパティ(メソッドも含む)を継承します。

JavaScriptの継承とは別のオブジェクトで定義されているプロパティを自分のプロパティのように使えることです。

Object()コンストラクタのプロトタイプオブジェクトはObject.prototypeオブジェクトです。
__proto__プロパティは、プロトタイプオブジェクトを参照します。

ですので、以下のようにObject.prototypeオブジェクトで定義されているtoString()メソッドをあたかも自分のメソッドのように使えます。

let book = new Object();
console.log(book.toString());
[object Object]

以下のようにコンストラクタを使わないで書いても、プロトタイプオブジェクトのプロパティが継承されます。

let book = {};
console.log(book.toString());
[object Object]

{}はオブジェクトリテラルといわれる記法ですが、コンストラクタを呼んだときと同じように、プロトタイプオブジェクトのプロパティが継承されます。

toString()メソッドと書きましたが、toString()プロパティと呼んでも間違いではありません。JavaScriptでは関数が割り当ててあるプロパティをメソッドと呼ぶからです。オブジェクトにあるのはすべてプロパティです。
ECMAScript 2015ではクラス構文が追加されましたが、本質的にはJavaScriptにはクラスはなく、プロトタイプ継承のコードに変換されます。シンタックスシュガーとして表面上クラス構文が導入されただけです。

スポンサーリンク

コンストラクタ、プロトタイプオブジェクト、オブジェクト(インスタンス)の関係

より理解をはっきりさせるために図解で整理します。

コンストラクタからうまれたオブジェクトはインスタンスといいます。ですが、図をみるとわかるのですが、コンストラクタとインスタンスは直接リンクしていません。インスタンスの__proto__プロパティが、プロトタイプオブジェクトを参照していて、プロトタイプオブジェクトのconstructorプロパティが、コンストラクタを参照しています。

コンストラクタのprototypeプロパティがプロパティオブジェクトを参照しています。

参照とは、プロパティの値にオブジェクトのメモリアドレスが代入されたものです。詳しくは「コンストラクタ、オブジェクトを参照する変数 ー ゼロから始めるJavaScript入門(ECMAScript 2015)【Vol.13】」を参照。

コンストラクタは関数です。JavaScriptの関数はオブジェクトでもあるので、本質的にはオブジェクトが3つあり、プロパティを介すことでリンクしています。

オブジェクト同士がプロパティを介してつながっているのが、JavaScriptのオブジェクト指向の特徴です。

インスタンスの__proto__プロパティは、正確にはインスタンスの内部プロパティ[[Prototype]]の値を参照しています。[[Prototype]]プロパティは、プロトタイプオブジェクトを参照しています。

プロトタイプオブジェクトに定義されているプロパティを上書き定義

以下のようにプロトタイプオブジェクトと同名のプロパティを上書き定義ができます。

let book = new Object();
book.toString = function() {
  return "bookです。";
};
console.log(book.toString());
bookです。

この場合、Object.prototypeオブジェクトのtoString()メソッドは呼び出されず、自分のオブジェクトのtoString()メソッドが呼びだれます。

ただし下記のようにdelete演算子でtoString()メソッドを消すと、Object.prototypeオブジェクトのtoString()メソッドが呼ばれます。

let book = new Object();

book.toString = function() {
  return "bookです。";
};

delete book.toString;

console.log(book.toString());
[object Object]

自作のコンストラクタでプロトタイプ継承

プロトタイプ継承を組み込みのコンストラクタで解説しましたが、自作のコンストラクタでもプロトタイプ継承が可能です。「js-study」フォルダ内に、「prototype2.html」というファイル名で保存します。

function Book(title, price) {
    this.title = title;
    this.price = price;
}

Book.prototype.getTitle = function() {
  console.log(this.title);
}

let book1 = new Book("三四郎", 600);
let book2 = new Book("こころ", 500);

console.log(book1);
console.log(book2);

実行すると以下のような結果になります。

コードは前回「コンストラクタ、オブジェクトを参照する変数 ー ゼロから始めるJavaScript入門(ECMAScript 2015)【Vol.13】」で書いたコードとほぼ同じですが、結果をみるとgetTitleメソッドが、プロトタイプオブジェクトに移っていることがわかります。

book1book2という2つのオブジェクトをコンストラクタからつくっています。2つのオブジェクトは、共通のプロトタイプオブジェクトを参照していて、getTitleメソッドはプロトタイプオブジェクトで定義されています。getTitleメソッドは、共通のメソッドになっているので、メモリが節約されます。

前回で書いたコードは、2つのオブジェクトのそれぞれにgetTitleメソッドがコピーされます。

Book.prototype.getTitle = function() {
  console.log(this.title);
}

コンストラクタに、prototypeプロパティを書き、さらに自作のプロパティを設定することでコンストラクタのプロトタイプオブジェクトにプロパティをつくることができます。

メソッドで試しましたが、プロパティでも同様に共有のプロパティをつくれます。

function Book(title, price) {
    this.title = title;
    this.price = price;
}

Book.prototype.stores = [];

let book1 = new Book("三四郎", 600);
let book2 = new Book("こころ", 500);

book1.stores.push("三省堂");
book2.stores.push("ジュンク堂");

console.log(book1.stores);
console.log(book2.stores);
["三省堂", "ジュンク堂"]
["三省堂", "ジュンク堂"]

storesプロパティは、プロトタイプオブジェクトに定義されているので、おのおののオブジェクトにはコピーされず、共有のプロパティとして参照されます。

配列でもプロトタイプ継承が使わている

ちなみに[“三省堂”, “ジュンク堂”]横の三角を開いてみましょう。
__proto__プロパティが見えます。開くと、多くのプロパティが見えます。これは配列はArrayコンストラクタからつくられたオブジェクトで、そのためArray.prototypeオブジェクトのプロパティを暗黙に継承しているのです。

配列で使うメソッドは、Array.prototypeオブジェクトのプロパティから継承されたものです。[]と書いた瞬間に、Arrayコンストラクタが呼び出され、Array.prototypeオブジェクトのプロパティが継承されます。

スポンサーリンク

関連記事

プロフィール

DESIGNMAP
ディレクター
ON VISITINGを制作・運営。
お問い合わせ