Search

検索したいワードを入力してください

2019年03月05日

JavaScriptのプロトタイプチェーンと継承の方法を解説

クラスやメソッドの継承はJavaScriptでの継承の概念であるプロトタイプチェーンについて、サンプルコードを例示して解説しています。プロトタイプとは何なのか、使い方やプロパティやメソッドを継承した場合にどのような動作をするのかといった特徴のご紹介です。

JavaScriptにおける継承とは

オブジェクト思考のプログラミング言語において、「継承」は強力なツールです。JavaScriptにおいては、プロトタイプチェーンという概念によって「継承」が実現されています。

プロトタイプとは、オブジェクトの継承元を表します。JavaScriptで表される値は全てオブジェクトですから、プロトタイプもJavaScriptオブジェクトとなります。

JavaScriptオブジェクトには必ずプロトタイプが存在するので、プロトタイプオブジェクトのプロトタイプを持っており、さらにそのプロトタイプがあり……と繋がっていきます。こうして鎖状に繋がっていくので、プロトタイプチェーンと呼ばれるわけです。

あるオブジェクトでsomeObject.keyのようにプロパティを参照しようとすると、JavaScriptエンジンはまずそのオブジェクトで定義されたプロパティを読みにいきます。

ここでkeyが未定義だった場合は、プロトタイプのkeyを探します。それでも未定義ならばさらにそのプロトタイプのkey……と遡っていき、見つかった値を返します。

このような仕組みにより、プロトタイプオブジェクトを継承したオブジェクトからは全てのプロトタイプに定義されたプロパティを参照することができます。

また、同名のプロパティを定義するだけで簡単にオーバーライドすることも可能です。

前置きが長くなりましたが、JavaScriptでプロトタイプチェーンを継承する具体的な方法を以下にご紹介します。

JavaScriptのプロトタイプチェーンと継承の方法の解説

JavaScriptで明示的にプロトタイプオブジェクトを指定してオブジェクトを生成するには、Object.create()メソッドを使います。以下に、Fruitオブジェクトをプロトタイプに持つAppleオブジェクトを作成する例を示します。

プロパティの継承

const Fruit = {
category: 'fruit',
name: 'fruit',
};

const Apple = Object.create(Fruit);

console.log(Apple.category); // -> 'fruit'
console.log(Apple.name); // -> 'fruit'

上のコードで参照しているcategoryやnameプロパティは、Appleオブジェクト上には定義されていませんが、プロトタイプであるFruitオブジェクトに定義されているので、参照することができます。

このように、JavaScriptオブジェクトでは継承元であるプロトタイプチェーン上の全プロパティにアクセスすることができます。

プロパティの継承は、継承先のオブジェクトで共通の値を使用したい場合などに有用です。

JavaScriptでのメソッドの継承

メソッドも他のプロパティと同様に継承されます。特筆すべき点は、thisの指すコンテキストがメソッドを呼び出したオブジェクトになるということです。

const Fruit = {
name: 'fruit',
reportName() {
console.log(this.name);
},
};

const Apple = Object.create(Fruit);
Apple.name = 'apple';

Fruit.reportName(); // -> 'fruit'
Apple.reportName(); // -> 'apple'

上の例では、AppleオブジェクトにreportName()メソッドは定義していないので、いずれの呼び出しでもFruitオブジェクトに定義したreportName()メソッドが呼び出されます。

ところが、プロトタイプメソッドを呼び出した場合のthisは呼び出し元のオブジェクトを表すため、Fruitオブジェクトから呼び出した場合にはFruitオブジェクトのnameが出力され、Appleオブジェクトから呼び出した場合にはAppleオブジェクトのnameが出力されます。

メソッドの継承により、共通の処理をプロトタイプに定義しておき、オブジェクトの状態に応じた処理を行わせる、オブジェクト指向の実現が容易になります。

オブジェクトの作成方法と発生するプロトタイプチェーン

JavaScriptでのオブジェクト生成にはいくつかの方法がありますが、生成されたオブジェクトのプロトタイプチェーンに着目してそれぞれ解説します。

構文構造によるオブジェクト生成

JavaScript上でオブジェクトを定義するといえば、リテラルを用いるのが王道でしょう。

const obj = {};

このオブジェクトのプロトタイプオブジェクトは、Object.prototype となります。ここでいうObjectとは、Objectというタイプのオブジェクトのコンストラクタ関数を表しています。

オブジェクトリテラルで作成されたオブジェクトは、Object.prototypeをプロトタイプに持つことにより、{}.hasOwnProperty()や{}.isPrototypeOf()などのビルトインメソッドを使うことができます。

コンストラクタ関数を用いる方法

JavaScriptでも、クラスベースの言語に似たnewというキーワードがあり、新しいオブジェクトを生成することができます。newを使って関数を実行すると、まず呼び出された関数のprototypeプロパティに定義されたオブジェクトをプロトタイプに持つオブジェクトが生成されます。

そして、生成されたオブジェクトをthisとして関数が実行されます。次の例では、Fruitというコンストラクタ関数からAppleオブジェクトを生成します。

function Fruit(name) {
this.name = name;
}

Fruit.prototype.reportName = function() {
console.log(this.name);
}

const Apple = new Fruit('apple');

Apple.reportName(); // -> 'apple'

関数を定義すると、prototypeという空のオブジェクトを持つプロパティが自動的に定義されます。このprototypeオブジェクトにプロパティやメソッドを追加することで、生成されるインスタンスオブジェクトのプロトタイプを定義することができます。

コンストラクタ関数を定義する場合、コンストラクタのprototypeにプロトタイプチェーンを定義することで、クラスベースと同様に機能の継承を行うことができます。以下の例では、Fruitコンストラクタを拡張してBerryコンストラクタを作成します。

function Fruit(name) {
this.name = name;
}

Fruit.prototype.reportName = function() {
console.log(this.name);
}

function Berry(name) {
Fruit.call(this, name);
}

Object.setPrototypeOf(Berry.prototype, Fruit.prototype);
Berry.prototype.reportCategory = () => console.log('berries');

const Strawberry = new Berry('strawberry');

Strawberry.reportName(); // -> 'strawberry'
Strawberry.reportCategory(); // -> 'berries'

BerryコンストラクタのprototypeオブジェクトのプロトタイプにFruitコンストラクタのprototypeオブジェクトを設定することにより、Fruit.prototypeに定義されているreportNameメソッドを呼ぶことができています。

なお、ES6以降ではclassキーワードが用意され、上記と全く同じ定義を下記のように書くことができるようになりました。

class Fruit {
constructor(name) {
this.name = name;
}
reportName() {
console.log(this.name);
}
}

class Berry extends Fruit {
reportCategory() {
console.log('berries');
}
}

const Strawberry = new Berry('strawberry');

Strawberry.reportName(); // -> 'strawberry'
Strawberry.reportCategory(); // -> 'berries'

Object.create メソッドを使ってプロトタイプを指定してオブジェクトを作る

既出ですが、Object.createメソッドを使うとプロトタイプを明示的に指定して新たなオブジェクトを生成することができます。プロトタイプを「型」として使うのではなく、データとして用いたい場合などに利用価値があります。

プロトタイプチェーンを上手に継承してコードもメモリも節約

ここまで、JavaScriptでのプロトタイプチェーンという継承の概念をご紹介しました。プロトタイプチェーンを理解して使いこなせば、コードの重複を防いだり、余計なオブジェクトの生成を避けたりすることができ、メンテナンス性向上やメモリ節約に繋がります。

この強力な機能を味方につけて、速くて正確な開発に役立てましょう。本稿が理解の一助となれば幸いです。

多くの人がプログラミングを諦めてしまう理由をご存知ですか?



近年プログラミングを勉強する人が増えています。

プログラミング学習者の多くは独学から取り組もうとしますが、だいたい80%ほどは3ヶ月も続かずに諦めてしまいます。早い人は1日目で。

多くの人がプログラミングを独学しようとして諦める理由は、次の3つ。
●モチベーションが維持できない
●エラーの原因・解決方法が分からない
●どう学習すればよいか分からない

TechBoostというプログラミングスクールでは、みんなと一緒にプログラミングをするのでモチベーションの維持ができ、分からないことがあればマンツーマンで教えてくれ、徹底的に研究された初心者向けの教材が揃っています。

TechBoostを卒業後、実際にエンジニアとして転職した方もいるほど。

本気でプログラミングを学びたい方は、一度無料のカウンセリングでご相談ください。プログラミングを嫌いになる前に。

tech boost卒業生インタビュー

tech boostの卒業生の声を聞きました。あなたがプログラミングを学びたい理由を、一度考えてみてください。
営業→Javaエンジニア→Rubyエンジニアと転向し、第一志望のFinTech企業で働く山下さん
元営業、ビジネスのわかるエンジニアを目指す菅原さん
サンフランシスコに交換留学し、シリコンバレーのVCでインターン中の梅本さん
予備校の営業から半年でエンジニア転職を果たした小田島さん

tech boostの口コミ



Related