JavaScriptのES6のクラス(class)キーワードの使い方と具体例

2021年3月9日JavaScript

ES6のクラス(class)キーワードの使い方と具体例を紹介します。

ES6のクラス(class)とConstructor関数の書き方の違い

クラスキーワードは、Constructor関数と背景で起こっていることは全く同じです。

クラスキーワードを使ってよりわかりやすい形にしただけの違いとなります。

まったく同じ内容を書いてみますので、比較してみましょう。

Constructor関数での書き方



{
  //Constructor関数での書き方
  const Bicycle = function (make, speed) {
    this.make = make;
    this.speed = speed;
  };

  Bicycle.prototype.accelerate = function (speed) {
    this.speed += 10;
    console.log(`${this.make}のスピードが${this.speed}に加速`);
  };
  Bicycle.prototype.brake = function (speed) {
    this.speed -= 10;
    console.log(`${this.make}のスピードが${this.speed}に減速`);
  };

  const bicycle1 = new Bicycle('自転車1', 20);
  console.log(bicycle1);//Bicycle {make: "自転車1", speed: 20}
  bicycle1.accelerate();//自転車1のスピードが30に加速
  bicycle1.accelerate();//自転車1のスピードが40に加速
  bicycle1.accelerate();//自転車1のスピードが50に加速
  bicycle1.brake();//自転車1のスピードが40に加速
  bicycle1.brake();//自転車1のスピードが30に加速
  bicycle1.brake();//自転車1のスピードが20に加速

}

 

ES6 クラスキーワードでの書き方



{
   //ES6 クラスキーワードでの書き方
   class Bicycle {
   constructor(make, speed) {
    this.make = make;
    this.speed = speed;
  }

  accelerate(speed) {
    this.speed += 10;
    console.log(`${this.make}のスピードが${this.speed}に加速`);
  }
  brake(speed) {
    this.speed -= 10;
    console.log(`${this.make}のスピードが${this.speed}に減速`);
  }
}

const bicycle1 = new Bicycle('自転車1', 20);
console.log(bicycle1);//Bicycle {make: "自転車1", speed: 20}
bicycle1.accelerate();//自転車1のスピードが30に加速
bicycle1.accelerate();//自転車1のスピードが40に加速
bicycle1.accelerate();//自転車1のスピードが50に加速
bicycle1.brake();//自転車1のスピードが40に加速
bicycle1.brake();//自転車1のスピードが30に加速
bicycle1.brake();//自転車1のスピードが20に加速
} 

最初の部分がこのように異なります。


  //Constructor関数での書き方
  const Bicycle = function (make, speed) { ... }

  //ES6 クラスキーワードでの書き方
   class Bicycle {
     constructor(make, speed) { ... }
   }

そして、メソッドの追加もConstructor関数とは異なり、ES6のクラスでは、クラスの{}の中にいれることができるのでシンプルになります。


  //Constructor関数での書き方
  Bicycle.prototype.accelerate = function (speed) { ... }

  //ES6 クラスキーワードでの書き方
  accelerate(speed) { ... }

 

クラス式とクラス宣言

クラスを記述するには、2つの形があります。

クラス式 class expression

const PersonCl = class {
};

クラス宣言 class declaration

class PersonCl {
}

このどちらでも大丈夫です。

以下では、クラス宣言の書き方で具体例を記述していきます。

ES6のクラス(class)キーワードの使い方と具体例



class PersonClass {
  constructor(firstName, birthYear) {
    this.firstName = firstName;
    this.birthYear = birthYear;
  }
//ここに書くことで、クラスでコピーされるのではなく、PersonClass.prototype.calc2050()と書くのと全く同じになります。
  calc2050() {
    console.log(2050 - this.birthYear);
  }
}

const tanaka = new PersonClass('Tanaka', 1959);
console.log(tanaka);
console.log(tanaka.__proto__ === PersonClass.prototype);//true

クラス宣言の書き方は次の通りです。

  1. class 名前{}をかく
  2. その中にconstructor(){}をかく
  3. その中には、プロパティをかく
  4. constructor(){}の外にメソッドをかく

といった感じです。

重要なポイントとしては、メソッドを書く位置です。
constructor(){}の外、かつclass 名前{}の中にメソッドを書くと、それはすべてのインスタンスにコピーされることなく、PersonClass.prototypeのプロパティとなります。
つまり、インスタンスではPersonClassからプロトタイプ継承して使えるメソッドということになります。

上のように、「tanaka.proto === PersonClass.prototype」がtrueであるということが、tanakaインスタンスのプロトタイプであることをことを示しています。

また、Constructor関数でメソッドを追加する次のやり方も使えます。



//上のcalc2050のように追加しても、下のようにprototypeを使うやり方でも全く同じです。
PersonClass.prototype.hello = function() {
  console.log(`こんにちは ${this.firstName}さん`);
}
tanaka.hello();//こんにちは Tanakaさん

クラスキーワードの注意点

  • クラスはホイスティングがありません。
  • クラスは第一級関数です。
  • クラスはstrictモードで実行されます。

ホイスティングに関しては、次の記事を参考にしてみてください。


第一級関数に関しては、次の記事を参考にしてみてください。

 

getterとsetter



{
   //ES6 クラスキーワードでの書き方
   class Bicycle {
   constructor(make, speed) {
    this.make = make;
    this.speed = speed;
  }

  accelerate(speed) {
    this.speed += 10;
    console.log(`${this.make}のスピードが${this.speed}に加速`);
  }
  brake(speed) {
    this.speed -= 10;
    console.log(`${this.make}のスピードが${this.speed}に減速`);
  }

  get speedUS() {
    return this.speed / 2;
  }
  
  set speedUS(speed) {
    this.speed = speed * 1.5;
  }

}

const bicycle1 = new Bicycle('自転車1', 20);
console.log(bicycle1);//Bicycle {make: "自転車1", speed: 20}
bicycle1.accelerate();//自転車1のスピードが30に加速
bicycle1.accelerate();//自転車1のスピードが40に加速
bicycle1.accelerate();//自転車1のスピードが50に加速

//getter
console.log(bicycle1.speedUS); //25
//bicycle1.speedUS();//値なので()で実行できない

//setter
bicycle1.speedUS = 20;
console.log(bicycle1);//Bicycle {make: "自転車1", speed: 30}
} 


getterはget メソッド(){}で設定することができます。
値を得たいときに使います。
呼び出すときは、getは値であり、メソッドではないためbicycle1.speedUS()するのではなく、プロパティとして扱います。

setterは必ず一つ引数をとります。
値を保存したいときなどに使います。
今回の場合、bicycle1.speedUS = 20とすることで、値を設定できています。
そのときに、setterで指定した条件に変換して設定できるので便利です。
setとgetの設定に関する「プロパティとディスクリプター」については次の記事を参考にしてみてください。

静的メソッド

インスタンス化を起こさずに呼び出すことができるメソッドを静的メソッド(static methods)といいます。

静的メソッドを利用すると、インスタンスを介さずに直接呼び出す定義が可能になります。

個々のインスタンスとは直接関係ないけれども、
必要な機能を作りたいといった場合に便利になります。
静的メソッドでは、thisが使えません。
クラス内で使われるthisはそのクラスから作られるインスタンスです。
静的メソッドはインスタンスを作らずに呼び出すので、thisを使うことができないません。

class Bicycle {
...
...
...
  static hi() {
    console.log('hi');
  }
}

使い方は、setやgetと同じ並びにstaticとして追加するだけです。

クラス継承

クラス継承とは、他のクラスのプロパティやメソッドを継承することです。
extendsとsuper()というキーワードを使います。
まずはextendsの役割を確認します。

extendsでプロトタイプを設定する



  //親クラス
  class PersonClass {
    constructor(firstName, lastName, birthYear) {
      this.firstName = firstName;
      this.lastName = lastName;
      this.birthYear = birthYear;
    }

    calc2050() {
      console.log(2050 - this.birthYear);
    }
    
    get year2030() {
      return 2030 - this.birthYear;
    }
  }

 
  //子クラス
  class StudentClass extends PersonClass {
  }

  const yamada = new StudentClass('Takeshi', 'Yamada', 2023, '国語');
  console.log(yamada);
//StudentClass {firstName: "Takeshi", lastName: "Yamada", birthYear: 2023}


extendsが、つまりはStudentClass.prototype = Object.create(PersonClassEx.prototype)と同じ役割を果たします。

super()でthisキーワードを子クラスに設定する

superは関数コンテキスト内で使用できる継承元の関数を呼び出すためのキーワードです。



  //親クラス
  class PersonClass {
    constructor(firstName, lastName, birthYear) {
      this.firstName = firstName;
      this.lastName = lastName;
      this.birthYear = birthYear;
    }
    calc2050() {
      console.log(2050 - this.birthYear);
    }
  }

//子クラス
  class StudentClass extends PersonClass {
    constructor(firstName, lastName, birthYear, course) {
      super(firstName, lastName, birthYear)
      this.course = course;
    }

    introduce() {
      console.log(`私の名前は${this.lastName} ${this.firstName}です。${this.course}を勉強しています。`);
      super.calc2050();//メソッドも継承できる。
    }
  }

  const yamada = new StudentClass('たけし', '山田', 2023, '国語');

  yamada.introduce();
  //私の名前は山田 たけしです。国語を勉強しています。
  // 27
  yamada.calc2050();// 27
  

Constructor関数のように、「PersonClass.call(firstName, lastName)」などとする必要はなく、super()だけでthisキーワードを変更でききます。
super()はthisキーワードを子クラスに設定するために必要なので常に最初に書きます。(メソッドを継承する場合は後でも可能です。)

メソッドチェーン:メソッドをチェーニングする(Chaining Methods)

map()やreduce()メソッドなどと同じようにクラスのメソッドもチェーン化することができます。これをメソッドチェーンといいます。
メソッドをチェーン化するためには、メソッドの最後に「return this;」とthisを返してあげます。
そして後はつなげるだけとなります。



{
//Challenge4
//親クラス
class Bicycle {
constructor (make, speed) {
  this.make = make;
  this.speed = speed;
}
  
  accelerate (speed) {
    this.speed += 10;
    console.log(`${this.make}のスピードが${this.speed}に加速`);
  }
  brake (speed) {
    this.speed -= 10;
    console.log(`${this.make}のスピードが${this.speed}に減速`);
    return this;
  }
}
//子クラス
  class BicyclePowerAssisted extends Bicycle {
    #battery;
  constructor (make, speed, battery) {
    super(make, speed);
    this.#battery = battery;
  }

  chargeBattery = function (chargeTo) {
    this.chargeTo = chargeTo;
  };
  
  accelerate = function(speed) {
    this.speed += 20;
    this.#battery --;
    console.log(`${this.make}のスピードが${this.speed}に加速, バッテリー消耗:${this.#battery}%`);
    return this;
  }  
} 


const dendo1 = new BicyclePowerAssisted('電動自転車1号', 120, 23);

dendo1.accelerate().accelerate().accelerate().brake().brake().brake();
// 電動自転車1号のスピードが140に加速, バッテリー消耗:22%
// 電動自転車1号のスピードが160に加速, バッテリー消耗:21%
// 電動自転車1号のスピードが180に加速, バッテリー消耗:20%
// 電動自転車1号のスピードが170に減速
// 電動自転車1号のスピードが160に減速
// 電動自転車1号のスピードが150に減速
}

accelerate()もchargeBattery()もBicycleのメソッドです。
戻り値がBicycleのインスタンスであれば、つなげることができます。
つまり、dendo1.accelerate()というものの戻り値がdendo1であればいいので、thisをreturnすればいいということです。
その後も同じなので、チェーンしたい場合は各メソッドでthisをreturnしておきます。

ちなみにaccelerate()メソッドは、親クラスと子クラスと名前は全く同じですが、内容は子クラスが上書きして変わっています。
これをポリモーフィズムといいます。
ポリモーフィズムに関してはこちらの記事を参照してみてください。