JavaScriptにおける継承
JavaScriptのオブジェクトは、他のオブジェクト(またはnull)をプロトタイプとして利用することができる。
オブジェクトのプロパティが参照された際、そのプロパティをオブジェクト自身が保持していなければ、代わりにプロトタイプのプロパティが参照される。
さらにプロトタイプのオブジェクトがそのプロパティを保持していない場合には、プロトタイプのプロトタイプを参照する。(nullに行き着くまで繰り返す)
このようにオブジェクトが他のオブジェクトのプロトタイプとして連鎖していく仕組みをプロトタイプチェーン(prototype chain)と呼ぶ。
JavaScriptでは、このプロトタイプチェーンを利用して継承を行う。
コンストラクタ関数とnew演算子のみで継承
まずは、シンプルに基底となるオブジェクト(コンストラクタ)を元に新しいオブジェクト(インスタンス)を生成する方法で継承をしてみる。
※途中に出てくる”arguments”オブジェクトは、関数呼び出しのタイミングで生成されて呼び出し元から与えられた引数の値を配列で保持している特別なオブジェクト
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | //基底オブジェクトを定義(プロトタイプ) function Pet(name) { //--コンストラクタの処理を定義-- //プロパティを設定 this.name = name; this.age = 0; console.log("基底オブジェクトのコンストラクタ"); } //基底オブジェクトのプロトタイプにメソッドを追加 Pet.prototype.countAge = function () { this.age += 1; }; //新しいオブジェクト(Cat)を定義 function Cat(name) { //基底オブジェクトのコンストラクタ呼び出し Pet.apply(this, arguments); //自身のコンストラクタの処理を定義 console.log("Catオブジェクトのコンストラクタ"); } //基底オブジェクトを継承 Cat.prototype = new Pet(); //新しいオブジェクト(Cat)のプロトタイプにメソッドを定義 Cat.prototype.sleep = function () { console.log(this.name + " is sleeping."); }; //Catオブジェクトを利用してインスタンス生成 console.log("-----Cat-----"); var cat = new Cat("santa"); cat.countAge(); cat.sleep(); console.log(cat.name + " is " + cat.age + " y.o."); |
上記のコードで継承を行っている部分は、”Cat.prototype = new Pet();”にあたる。
うまく動作するように見えるが、実際に実行すると”—–Cat—–“の出力の前に”基底オブジェクトのコンストラクタ”が出力される。
これは、”Cat.prototype = new Pet();”のタイミングで基底オブジェクトのコンストラクタが実行されてしまう為である。
これを回避する為には、以下のように継承部分を変更する必要がある。
1 2 3 4 5 | //基底オブジェクトを継承 var Func = function () {}; Func.prototype = Pet.prototype; Cat.prototype = new Func(); Cat.prototype.constructor = Cat; |
まず、ダミーのオブジェクト”Func”を定義する。
次に”Func”のプロトタイプに基底オブジェクトのプロトタイプを設定する。
さらに新しいオブジェクト”Cat”のプロトタイプに”Func”から生成したインスタンス(つまり基底オブジェクトのプロトタイプ)を設定する。
最後に新しいオブジェクト”Cat”のプロトタイプに自身のコンストラクタを設定する。
これで継承時に基底オブジェクトのコンストラクタが実行されることがなくなる。
Object.createを利用して継承
上記の継承をECMAScript5で定義されたObject.createを利用すると以下のように表すことができる。(新しいオブジェクト”Cat”のほかに別のオブジェクト”Dog”も追加している)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 | //基底オブジェクトを定義(プロトタイプ) function Pet(name) { //--コンストラクタの処理を定義-- //プロパティを設定 this.name = name; this.age = 0; console.log("基底オブジェクトのコンストラクタ"); } //基底オブジェクトのプロトタイプを拡張 Pet.prototype = Object.create(Object.prototype, { //プロパティの設定を拡張 name: { writable: true }, age: { writable: true }, //自身のコンストラクタをセット constructor: { value: Pet, enumerable: false, writable: true, configurable: true }, //メソッドを定義 countAge: { value: function () { this.age += 1; } } }); //新しいオブジェクト(Cat)を定義 function Cat(name) { //基底オブジェクトのコンストラクタ呼び出し Pet.apply(this, arguments); //自身のコンストラクタの処理を定義 console.log("Catオブジェクトのコンストラクタ"); } //基底オブジェクトのプロトタイプの継承と拡張 Cat.prototype = Object.create(Pet.prototype, { //自身のコンストラクタをセット constructor: { value: Cat, enumerable: false, writable: true, configurable: true }, //メソッドを定義 sleep: { value: function () { console.log(this.name + " is sleeping."); } } }); //新しい別のオブジェクト(Dog)を定義 function Dog(name) { //基底オブジェクトのコンストラクタ呼び出し Pet.apply(this, arguments); //自身のコンストラクタの処理を定義 console.log("Dogオブジェクトのコンストラクタ"); } //基底オブジェクトのプロトタイプの継承と拡張 Dog.prototype = Object.create(Pet.prototype, { //自身のコンストラクタをセット constructor: { value: Dog, enumerable: false, writable: true, configurable: true }, //メソッドを定義 bark: { value: function () { console.log(this.name + " is barking."); } } }); //Catオブジェクトを利用してインスタンス生成 console.log("-----Cat-----"); var cat = new Cat("santa"); cat.countAge(); cat.sleep(); console.log(cat.name + " is " + cat.age + " y.o."); //Dogオブジェクトを利用してインスタンス生成 console.log("-----Dog-----"); var dog = new Dog("reon"); dog.countAge(); dog.bark(); console.log(dog.name + " is " + dog.age + " y.o."); |
Object.createの第一引数に基底オブジェクトのプロトタイプを、第二引数に新しいオブジェクト自身のコンストラクタを指定して生成したオブジェクトを新しいオブジェクトのプロトタイプに設定している。(メソッドなどを追加する場合も第二引数に列挙する)
Object.createでオブジェクトを生成する際には、コンストラクタは実行されないので継承時に基底オブジェクトのコンストラクタが実行されることはない。
TypeScriptはC#風に書ける。privateやpublicなどを使いC#風に書いてみる。ビルドが通る。ほとんどC#と一緒だ