Javascript classのまとめ(基本、static、extends、super)

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)を追加したい時は以下が必要。 
    1. 最初はconstructor( )内に継承するプロパティと共に新しいいプロパティを列挙する事。
    2. 次に、新しいプロパティ(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

コメント