Javascriptはprototypeと[[Prototype]]を使って関数をclassの様に使えたので、JavascriptにはClassがなかったのですがES2015から他の言語の様に記述出来るclassが追加されたようです。 classの書き方の方が簡単なのでまとめてみました。 間違っていたらコメント頂けたら嬉しいです。
classの書き方
- classは構造のひな型で実体はない。 new Class名で、そのclassと同じ構造持ったインスタンスという実体ができる。 classはスタンプの様なもの。 同じ構造のインスタンスを沢山作れる。
- インスタンスは実体があるので、インスタンス数xメモリでメモリを消費する。 なので内部の変数も保持できる。 関数は処理が終わると変数も破棄するので保持できないが、処理終了でメモリも解放される。
- Class名は半角英文字で始まる。 メソッドや変数との違いが分かるよう、最初の文字は大文字が推奨。
- constructorで生成したインスタンス内部で保持できる変数を設定する。 class内でthisを使って活用できる。(下の例ではthis.nameや this.price) class外では、インスタンス名.変数名で使える。(下の例では、a.nameやa.price)
- メソッド(関数)を設定。
// これがclass。 class Fruits { constructor(name,price){ this.name = name; this.price =price; } func1() { console.log("yummy"); } func2(a,b){ console.log(this.name,a*b); } with_tax(a){ return this.price*(1+a); } } var a = new Fruits('apple',200); // ひな型のFruite classからaという実体のあるインスタンスを生成 console.log(Fruits); //-> [class Fruits] console.log(a); //-> Fruits { name: 'apple', price: 200 } console.log(a.name, a.price); //-> apple 200 a.func1(); //-> yummy a.func2(10,2.5); //-> apple 50 console.log(a.with_tax(0.08));//-> 216
- newでインスタンスを作った時に、引数に入れた値が実体のプロパティに入る。
- ひな型のclassにあるstatic以外のメソッドは隠れている内部プロパティの[[Prototype]]に入る。
- なので、インスタンスをconsole.log()で出力しても、元のClass名と実体のプロパティしか表示されない。
- しかし、実際func1等のメソッドをa.func1()で実行すると、[[Prototype]]のfunc1を参照している。
classからインスタンス生成の内部(図解)
こんな感じに見える。 実際、classFruitsの中を見たら、全てprototypeに入っていて、インスタンスaの中を見たら、メソッド等[[Prototype]]に入っていた。
static
メソッドにstaticをつけるとclass.メソッド名()でしか実行できない。 インスタンス名.メソッド名()では実行できないし、インスタンス内も実行できない。
- staticのメソッドはclassから直接しか実行できない。
- staticでないメソッドはインスタンスからしか実行できない。
- classでしか実行できないようにしたい時。 newなどは多分隠しstaticメソッドなのかな?
class Fruits { constructor(name,price){ this.name = name; this.price =price; } func1() { console.log("yummy"); } static func2(){ //ここがstatic return 'wow'; } func3(){ this.func1(); this.func2(); } } const a = new Fruits('apple',200); // Fruitsクラスのインスタンスaを生成 a.func1(); //-> yummy a.func2(); //-> TypeError: a.func2 is not a function a.func3(); //-> yummy this.func1()を実行 //-> TypeError: this.func2 is not a function console.log(Fruits.func2()); //-> wow console.log(Fruits.func3()); //-> TypeError: Fruits.func3 is not a function
class継承
基本の形
- extendsで親クラスを継承して子クラスを作れる。
- super()で、親クラスと同じ名前のプロパティやメソッドがなければで親classから全て継承して、子でも使える。
class Fruits { constructor(name,type){ this.name = name; this.type = type; } func1() { console.log(this.type,this.name); } } class AppleClass extends Fruits{ constructor(name, qty, price, type){ super(name, type); this.price =price; this.qty = qty; } func3(){ super.func1(); console.log(`${this.name}: ${this.price*this.qty}円`); } } const a = new AppleClass('lemon',30,20,'Fruits'); a.func3(); //-> Fruits lemon //-> lemon: 600円
super()で親classのthisを呼び出す
- super()は2つの機能があります。
- thisとして親classオブジェクトを継承先(子)に呼び出すもの。 なのでsuper()の前にthisを使うとエラーになる。
- constructor内でsuper(name, type)とするとname, typeプロパティを親classと子classで共有する。
- 新しいプロパティ(qty)を追加したい時は以下が必要。
- 最初はconstructor( )内に継承するプロパティと共に新しいいプロパティを列挙する事。
- 次に、新しいプロパティ(this.qty=qty;)でthisが必要となるので、その前にsuper();を宣言してthisを持ってくる必要がある。
- 親classが元になるので、super()をconstructorの最初に入れないとthisを持ってこれず、thisを使えない。
- 親classと同じメソッド名(func1)があった場合、親classのメソッドは呼び出されない。
function型の継承と比べてみる
- 書き方が違うが、同じような動作になる。
- 再度継承しても同じような動作になる。 b.constructor()の部分。 (下の『おまけ』にもう少書いてあるのでよかったら見てくださいね。)
- でもclassの方が書き方も簡単だし、classではsuperが使えるのが便利。
- function型オブジェクトの場合は、superを使えないので、親と子で同じ名前のメソッドがあった時、子のメソッドが採用され親の同名のメソッドを使えません。
- でもES2015以前に書かれたコードを読み解く時にはfunction型オブジェクトも分かっていた方がいいような気がします。
class Fruits { constructor(name, price){ this.name = name; this.price = price; } func1(){ console.log("yummy"); } } function Fruits2(name, price){ this.name = name; this.price = price; } Fruits2.prototype.func1 = function(){ console.log('yummy'); } const a = new Fruits('lemon',200); const b = new Fruits2('lemon',200); console.log(a); //-> Fruits { name: 'lemon', price: 200 } console.log(b); //-> Fruits2 { name: 'lemon', price: 200 } a.func1(); //-> yummy b.func1(); //-> yummy const c =new b.constructor('lemon', 200); console.log(c); //-> Fruits2 { name: 'lemon', price: 200 } c.func1(); //-> yummy
classとfunction型オブジェクトを作ってVScodeで中身を比較してみたら動作は同じだけどちょっと違ってました。
おまけ new a.constructor();
- これでも出来た。
- ちゃんと{name:’banana’, price:123}が入ってインスタンス bが出来た。
- b.func1()とすると、ちゃんと’yummy’が表示される。
- どんどん数珠繋ぎ的に作れた。
class Fruits { constructor(name,price){ this.name = name; this.price =price; } func1() { console.log("yummy"); } } const a = new Fruits('apple',200); //これでしか作れない const b = new a.constructor('banana',123); //エラーは出ずに作れたが.... //const c =new a('lemon','234');console.log(a); //TypeError: a is not a constructor 全く作れない console.log(a); //-> Fruits {name: 'apple', price: 200} console.log(b); //-> Fruits {name: 'banana', price: 123} b.func1(); //-> yummy const c = new b.constructor('lemon',500); //これも作れた console.log(c); //-> Fruits {name: 'lemon', price: 500} c.func1(); //-> yummy
コメント