こんにちは、ライターのマサトです!
今回は、オブジェクト指向の言語でよく使われる概念の「継承」について学習していきましょう!
この記事では、
・「継承」とは?
・「継承」の使い方
という基本的な内容から、
・「call」を利用した継承
・「class」を利用した継承
・継承した機能をオーバーライドする方法
などの応用的な使い方に関しても解説していきます。
この記事で、「継承」をしっかり学習して自分のスキルアップを目指しましょう!
「継承」とは?
それでは、まず最初に「継承」についての基本的な知識から学習を進めていきましょう!
JavaScriptに限らず、オブジェクト指向言語であれば「継承」という概念は必ず出てくる重要なキーワードです。
簡単に言ってしまえば任意のオブジェクトが持つ機能を別のオブジェクトから利用するためのテクニックと言えるでしょう。
言葉で説明するよりも、コードを見ながら学習する方が分かりやすいので、まずは簡単なオブジェクト例を見てください!
var Human = function() {} Human.prototype.speak = function() { console.log('こんにちは!'); } var taro = new Human(); taro.speak();
実行結果
こんにちは!
この例では、Human(人間)という空のオブジェクトを作り、prototypeを利用して文字列を出力するだけの「speak」という機能を持っています。
そして、「taro」というインスタンスを作れば、taro.speak()と記述するだけで元になっているHumanオブジェクトが持っている「speak」機能が使えます。。
つまり、Humanオブジェクトは文字列を出力するだけの「speak」という機能を持った簡単なオブジェクトというわけです。
そして、今度は「Boy」オブジェクトを作ってインスタンス化してみましょう!
var Human = function() {} Human.prototype.speak = function() { console.log('こんにちは!'); } var Boy = function() {} var taro = new Boy(); taro.speak();
実行結果
Uncaught TypeError: taro.speak is not a function
今度は、Humanオブジェクトは使わずに、新しく作成した「Boy」を使って「taro」というインスタンスを作りました。
コードを見ると分かりますが、この「Boy」オブジェクトの中身は「空っぽ」で何も機能を持っていないため、taro.speak()と記述するとエラーになるわけです。
そこで、Humanオブジェクトが持っている「文字列を出力する機能」を、「Boy」オブジェクトからも利用できるようになればいいですよね?
このような時に「継承」を使えばいいわけです!
「継承」の使い方
この章では、基本的な継承パターンについて学習をしていきましょう。
主に、PrototypeやObject.createを使った手法について学んでいきます。
prototypeを使った継承
先ほどの「Boy」オブジェクトから、Humanオブジェクトが持つ「文字列を出力する機能」を使えるようにしていきます。
次のコード例を見てください!
var Human = function() {} Human.prototype.speak = function() { console.log('こんにちは!'); } var Boy = function() {} //Humanオブジェクトの機能をBoyに引き継ぐ Boy.prototype = new Human(); var taro = new Boy(); taro.speak();
実行結果
こんにちは!
この例で最も注目するべきは「Boy.prototype = new Human()」の箇所です!
この1行を記述するだけで、Humanオブジェクトが持っていた「speak」機能をBoyオブジェクトから利用できるようになるわけです。
これは、Boyオブジェクトが持つprototypeオブジェクトに、Humanオブジェクトが持つ機能を追加するようなイメージに近いでしょう。
実行結果を見ると分かるように、しっかりとBoyオブジェクトのインスタンスから「speak」機能が使えていますね。
Object.createを使った継承
もう1つよく使う「継承」の方法として、「Object.create」があるのでご紹介します!
こちらも先ほどの継承方法と同じように、任意のprototypeオブジェクトに機能を追加するようなイメージで簡単に利用できます。
次のコード例を見てください!
var Human = function() {} Human.prototype.speak = function() { console.log('こんにちは!'); } var Boy = function() {} //Humanオブジェクトの機能を引き継ぐ Boy.prototype = Object.create(Human.prototype); var taro = new Boy(); taro.speak();
実行結果
こんにちは!
この例では、「Object.create」の引数にHumanオブジェクトのprototypeを指定していますね。
これにより、BoyのprototypeにHumanが持つ機能を追加することができるわけです。
実行結果からも分かるように「speakメソッド」を利用できていますね。
Object.createでメソッドを同時に追加する方法
実は「Object.create」には便利な機能があります!
それは、「継承」によって機能を引き継ぐだけでなくメソッドを同時に追加できるという点です!
次のコード例を見てください
var Human = function() {} Human.prototype.speak = function() { console.log('こんにちは!'); } var Boy = function() {} Boy.prototype = Object.create(Human.prototype, { //新しくsayhelloメソッドを追加 sayhello: { value: function() { console.log('Hello!!'); } } }); var taro = new Boy(); taro.speak(); taro.sayhello();
実行結果
こんにちは! Hello!!
この例では、Object.createの第2引数に新しく「sayhello」メソッドを追加する記述を追記しています。
これにより、Humanオブジェクトが持っていた「speak」メソッドと、新しく追加した「sayhello」メソッドの両方が利用可能になったわけです。
このように単純な継承だけでなく、より複雑なオブジェクト構造を形成できるのも「Object.create」のメリットになるでしょう。
「call」を利用した継承
この章では、「call」を使った継承パターンについて見ていきましょう!
「call」を利用するケースについてと、実際のプログラミング手法につてい学んでいきいます。
継承されない初期値のプロパティについて
基本的な「継承」を学習したところで、今度は「call」の使い方について学習していきましょう!
「call」の使い方は簡単なのですが、その概念が少し難しいので順を追って解説していきます。
これまでのサンプルでは、継承元になっていた「Human」オブジェクトの中身は空っぽでした。
しかし、本来はさまざまな初期値(プロパティ)が設定されているはずです。
そこで、Humanオブジェクトに「名前」を保持するプロパティを設定してみましょう!
次のコード例を見てください!
var Human = function(str) { //「name」に名前を保持する this.name = str; } Human.prototype.speak = function() { //名前を一緒に出力する console.log('こんにちは、'+this.name+'さん!'); } //名前を「太郎」にする var taro = new Human('太郎'); taro.speak();
実行結果
こんにちは、太郎さん!
この例では、Humanオブジェクトの中に「this.name」という初期値を設定し、インスタンス化する時に名前を保持できるようにしています。
そして、「speak」メソッドを実行すると、指定した名前と一緒に実行結果のような文字列を出力します。
このようなHumanオブジェクトを、これまで学習してきた方法で「継承」するとどうなるでしょうか?
次のコード例を見てください!
var Human = function(str) { this.name = str; } Human.prototype.speak = function() { console.log('こんにちは、'+this.name+'さん!'); } var Boy = function() {} Boy.prototype = new Human(); var taro = new Boy('太郎'); taro.speak();
実行結果
こんにちは、undefinedさん!
この例では、Humanオブジェクトを「Boy」のprototypeを使って継承しているのが分かります。
しかしながら、実行結果を見ると分かるように「名前」が「undefined」になっており、正しく名前が保持できていないのが分かります。
実は、これまでの「継承」方法だとprototypeオブジェクトの機能は引き継がれるのですが、継承元の初期値(プロパティ)は引き継がれないのです!
そこで、利用したいのが本章で学ぶ「call」です。
「call」を使って継承を行う方法
それでは、「call」を使った継承パターンについて見ていきましょう!
「call」を使うと、継承元の初期値(プロパティ)を簡単に呼び出せるようになります。
次のコード例を見てください!
var Human = function(str) { this.name = str; } Human.prototype.speak = function() { console.log('こんにちは、'+this.name+'さん!'); } var Boy = function(name) { //Humanオブジェクトの初期値を呼び出す Human.call(this, name); } Boy.prototype = new Human(); var taro = new Boy('太郎'); taro.speak();
実行結果
こんにちは、太郎さん!
このコード例では、空っぽだったBoyオブジェクトの中に、「Human.call()」メソッドを記述しています。
これにより、Humanオブジェクトが持っていた初期値をBoyオブジェクト内でも使えるようにしているわけです。
「call」の引数には「this」を指定していますが、これによりHumanオブジェクトのthisを参照することができます。
さらに、第2引数として「name」を指定しています。
これはHumanオブジェクトが文字列を受け取るように設定されていることに起因します。
なので、同じようにBoyオブジェクトでも名前を受け取れるように変数「name」を指定しているわけです。
最終的に、BoyオブジェクトはHumanオブジェクトが持つ初期値とprototypeオブジェクトが持つ機能の両方を引き継いだことになるのです。
「class」を利用した継承
この章では、ES2015(ES6)から利用できるようになった「class」を使った継承パターンについて見ていきましょう!
主に、「extends」「super」の使い方について学んでいきいます。
「extends」を利用して継承を行う方法
それでは、「class構文」を使って簡単な継承を作ってみましょう!
まずはメソッドだけが定義されたclassを用意します。
class Sample { showMessage() { console.log('Sampleのメソッド'); } }
この例では、「showMessage()」という文字列を出力するだけのメソッドを定義しています。
このSampleクラスを継承するには「extends」を利用します。
次のサンプル例を見てください!
class SampleSeconds extends Sample {} var test = new SampleSeconds(); test.showMessage();
実行結果
Sampleのメソッド
新しく「SampleSeconds」クラスを作成していますが、extendsを使って「Sampleクラス」を継承していますね。
実行結果を見ると、親クラスに定義されていた「showMessage()」メソッドが利用できているのが分かります。
このように「extends」で親クラスを記述するだけなので、非常にお手軽な方法と言えるでしょう。
ちなみに、class構文について基本から学びたい方は次の記事でまとめているので参考にしてみてください!
「super」で親クラスを利用する方法
「super」を使うと親クラスのコンストラクターやメソッドへ簡単にアクセスできるので合わせてご紹介しておきます!
まず、親クラスのコンストラクターに定義していたプロパティを呼び出す方法から見ていきましょう。
次のようなclassがあるとします。
class SampleOne { constructor(myname, myage) { this.name = myname; this.age = myage; } getName() { return this.name; } }
プロパティとして「name / age」が、メソッドとして「getName()」が定義されていますね。
このクラスを継承した際に、親クラスのコンストラクターを呼び出すには次のように記述します。
class SampleThirds extends SampleOne { constructor(myname, myage) { super(myname, myage); console.log( this.name ); console.log( this.age ); } } var taro = new SampleThirds('太郎', 30);
実行結果
太郎 30
この例では、コンストラクター内に「super(myname, myage)」と記述しているのが分かります。
これにより、親クラスの「name / age」プロパティに「myname / myage」を定義することができます。
そのため、「SampleThirdsクラス」はプロパティの定義を改めて記述する必要はないというわけです。
さらに、親クラスのメソッドを呼び出すことも簡単です。
class SampleThirds extends SampleOne { getMyName() { return '私の名前は、' + super.getName() + 'です!'; } } var test3 = new SampleThirds('太郎', 30); console.log(test3.getMyName());
実行結果
私の名前は、太郎です!
この例では、「super.getName()」と記述することで親クラスに定義されているメソッドを呼び出しています。
結果的に、「getName()」メソッドを利用して新たなメソッドを作成しているのが分かりますね。
継承した機能をオーバーライドする方法
みなさんは、「オーバーライド」という言葉を聞いたことがあるでしょうか?
直訳すると「上書き」という意味になりますが、ここで解説する「オーバーライド」は継承によって引き継がれた機能を、独自に上書きして新しい機能に作り変えることを意味しています。
例えば、これまでHumanオブジェクトが持っていた「speak」メソッドの機能を思い出してください。
var Human = function() {} Human.prototype.speak = function() { console.log('こんにちは!'); }
この「speak」メソッドを継承したオブジェクトを作り、その後に「speak」メソッドの中身を独自に変更して上書きする方法をご紹介します。
次のコード例を見てください!
var Human = function() {} Human.prototype.speak = function() { console.log('こんにちは!'); } var Boy = function() {} Boy.prototype = new Human(); //新しく「speak」メソッドを書き換える Boy.prototype.speak = function() { console.log('こんにちは、Boyオブジェクト!'); } var taro = new Boy(); taro.speak();
実行結果
こんにちは、Boyオブジェクト!
この例では、HumanオブジェクトをBoyオブジェクトに継承しています。
そして、「Boy.prototype.speak」を再定義して出力する文字列を変更しているのが分かりますね。
このように記述することで、継承元が持っていた機能を変更したり追記することが可能になるという点は重要な要素となるので、ぜひ覚えておきましょう!
まとめ
今回は、オブジェクトの「継承」について学習してきました!
最後に、もう1度ポイントを振り返ってみましょう。
・「継承」とは、別のオブジェクトが持つ機能などを引き継いで利用すること
・callを使うことで、継承元が持っていた初期値などを引き継ぐことができる
・class構文を利用することで簡単に継承が実現できる
・継承元のメソッドなどは、オーバーライド(上書き)することができる
これらの内容を踏まえながら、ぜひ自分のプログラムにも積極的に取り入れて活用できるように頑張りましょう!