JavaScript入門 クラス

最終更新日

クラスの記法はコードを整理したり、同じ構造のオブジェクトを複数作成したりするときに使います。

クラスの基本

クラストは、オブジェクトを作成するためのひな形のようなものです。クラスには、オブジェクトに設定したいプロパティやメソッドを定義でき、それをもとにオブジェクトを簡単に作成できます。また、オブジェクトの作成時に実行される処理(コンストラクタ)を実装することにより、プロパティに設定される値などオブジェクトごとに変更できます。これにより、ひな形のクラスを1つ作成するだけで、同じような構造の複数のオブジェクトを簡単に作成できます。

オブジェクトリテラルでは毎回定義する必要がある

//ユーザー情報を管理するオブジェクトを仮定した場合
//ユーザー:山田太郎
const taro = {
    username: "山田太郎",
    password: "taro-pad",
    login(){
        console.log( `ログイン [ ${this.username} / ${this.password} ]` );
    }
}
//ユーザー:山田花子
const hanako = {
    username: "山田花子",
    password: "hanako-pad",
    login(){
        console.log( `ログイン [ ${this.username} / ${this.password} ]` );
    }
}

これをクラス記法で書き直す場合

//ユーザーを作成するクラスの定義
class User {
    constructor( username, password ){
        this.username = username;
        this.password = password;
    }
    login() {
        console.log( `ログイン [ ${this.username} / ${this.password} ]` );
    }
}
const taro = new User("山田太郎","taro-pad");  //オブジェクトの作成:山田太郎
const hanako = new User("山田花子","hanako-pad");  //オブジェクトの作成:山田花子

コードの重複部分がなくなり、コードのメンテナンスがしやすくなる

オブジェクトの作成に必要な情報をクラスにまとめることによって、同じようなコードを何回も記述する必要がなくなります。

オブジェクトの作成が簡単に行えるようになる

クラスからオブジェクトを作成するコードは、const taro = new User(“山田太郎”,”taro-pwd”);のように1行で済みます。

クラスの定義

構文:クラスの記法

まずクラスには、コンストラクタを記述します。コンストラクタは、オブジェクトの生成が行われるときに実行されれう関数(メソッド)です(❶)。そのため、コンストラクタ内で生成されるオブジェクトに設定したプロパティを定義します。これは、thisに対してプロパティを設定することで実現できます(❷)。また、生成されたオブジェクトにメソッドを追加したい場合には、クラス直下に記述します(➌)。

インスタンス化

クラスからオブジェクトを生成する処理をインスタンス化と呼びます。また、インスタンス化によって生成されるオブジェクトは、インスタンスと呼びます。インスタンス化によってインスタンスを作成するには、new演算子を使います。

構文:インスタンス化によってオブジェクトを生成する

引数あり
const オブジェクト = new クラス名( [ 引数1,引数2,・・・ ] );
引数なし
const オブジェクト = new クラス名

new演算子でインスタンス化を行う場合の処理

❶new演算子により、クラスのコンストラクタが実行されます。このとき、クラス名に続く()に渡された値は、コンストラクタに引数として渡されます。

❷コンストラクタの実行を終えると、その中で使われているthisがnew演算子の結果として返されます。そのため、生成されるインスタンスにプロパティを設定したい場合には、「this.プロパティ」のようにthisに対してプロパティを追加します。

➌コンストラクタの中でthis.propに値を設定しているため、返されるインスタンスは{ prop: “引数” }になります。

ドット記法などを用いてプロパティの値の取得・変更を行うことができます。

class TestCls{

    constructor( arg ){
        this.prop = arg;
    }

}

const obj = new TestCls( "引数" );   //インスタンス化
console.log( obj.prop );
>引数                                //クラス内でthis.propに設定した値が取得される

obj.prop = "値の変更";               //オブジェクトのプロパティの値を変更
console.log( obj.prop );
>値の変更

コンストラクタ関数内でreturn文が使われている場合

コンストラクタ内にreturn文が存在し、そのreturn文によってオブジェクトが返された場合には、そのオブジェクトがnew演算子の戻り値になります。ただし、通常、コンストラクタ内ではreturn文は使わず、thisを使ってインスタンスオブジェクトを作成します。

class TestCls{
    constructor() {
        this.prop = "値";    //return文があるためthisは無視される
        return { test: "returnで返されたオブジェクト" };
    }
}
console.log( new TestCls );
>{ test: "returnで返されたオブジェクト" };

メソッド定義

クラスで定義したメソッドは、インスタンスから実行できます

class TestCls{
    method( arg ){
        console.log( `引数:[ ${ arg } ]でメソッドを実行` );
    }
}
const obj = new TestCls;
obj.method( "テスト" );
>引数:[ テスト ]でメソッドを実行 

コンストラクタを記述していないため、objオブジェクトはプロパティを持ちません。しかし、method(・・・){・・・}をクラスに定義することで、obj.method()でメソッドを実行できます。

また、メソッド内でも、thisを通してインスタンスのプロパティの取得・変更を行うことができます。

class User {
    constructor( username, password ){
        this.username = username;   //インスタンスプロパティに値を取得
        this.password = password;   //インスタンスプロパティに値を取得
    }
    login() {
        console.log( `ログイン [ ${this.username} / ${this.password} ]` );//プロパティに設定された値を取得
    }
    changePassword( pwd ){
        this.password = pwd;   //プロパティの値変更
        console.log( `パスワードが[ ${this.password} ]に変更されました。` );
    }
}
const taro = new User("山田太郎","taro-pad");

taro.login();
>ログイン [ 山田太郎 / taro-pwd ]

taro.changePassword( "new-pwd" );
>パスワードが[ new-pwd ]に変更されました。

taro.login();
>ログイン[ 山田太郎 / new-pwd ]

上記のコードでは、loginメソッドやchangePasswordメソッド内のthisはtaroオブジェクトを参照しています。メソッドとして実行されたときのthisは、呼び出し元オブジェクトを参照するので、loginメソッドを実行した時のthisの参照先はtaroオブジェクトになります。

複数インスタンスでそれぞれ同じメソッドを実行した時も、thisはそれぞれ呼び出し元オブジェクトを参照します。

また、オブジェクトのメソッド内から、同じオブジェクトの他のメソッドを実行するときにも、thisを使います。

メソッド内から他のメソッドを実行

class User {
    constructor( username, password ){
        this.username = username;   //インスタンスプロパティに値を取得
        this.password = password;   //インスタンスプロパティに値を取得
    }
    login() {
        this.check();  //thisを通して他のメソッドを実行
        console.log( `ログイン [ ${this.username} / ${this.password} ]` );
    }
    check(){
        console.log( `ログイン情報をチェックします。` );
    }
   
}
const taro = new User("山田太郎","taro-pad");
taro.login();
>ログイン情報をチェックします。
>ログイン [ 山田太郎 / taro-pwd ]
クラス名はパスカルケース(単語の先頭大文字)を使いますが、プロパティ名やメソッド名はキャメルケース(単語の先頭小文字)を使います。

クラスに関わるその他の実装

静的メソッドと静的プロパティ

静的メソッドとは、インスタンス化が不要で、そのままクラスから利用できる静的プロパティと静的メソッドについて解説します。静的プロパティはスタティックプロパティ、静的メソッドはスタティックメソッドと言います。

以下の特徴があります。

  • 静的メソッドから他の静的メソッドや静的プロパティは、クラス名を使って参照します。
  • 静的メソッドからインスタンスのメソッドやプロパティは参照できません
  • インスタンスメソッドから他の静的メソッドや静的プロパティは、クラス名またはthis.constructorを使って参照できます。
  • インスタンスメソッドから他のインスタンスのメソッドやプロパティは、thisを通して参照します。

静的プロパティや静的メソッドを定義するには、メソッド名の先頭にstaticキーワードを付けます。

構文:静的メソッド、静的プロパティの定義方法

class クラス名 {
    static プロパティ名 = 値;
    static メソッド名(){・・・}
}

構文:静的メソッド、静的プロパティの利用方法

クラス名.プロパティ名;
クラス名.メソッド名();

使用例

class Human {
    static TYPE = "普通の人";     //静的プロパティを定義
    static staticMove(){         //静的メソッドを定義
        console.log( Human.TYPE + "は歩いて移動します。" );
    }
    constructor( name ){
       this.name = name;
    }
    move(){                      //インスタンスメソッド
       console.log( this.name + "は歩いて移動します。" );
    }
}

const taro = new Human( "太郎" );
Human.staticMove();
>普通の人は歩いて移動します。
console.log( Human.TYPE );
>普通の人
taro.move();
>太郎は歩いて移動します。

静的プロパティや静的メソッドにアクセスする場合には、クラス名(Human)に続けてプロパティ名、メソッド名を記述します。オブジェクト(taro)に続けて静的プロパティや静的メソッドを記述することはできません。

そのため、静的メソッド内では、thisを使ってもインスタンスを取得できません。thisキーワードが使えるのは「オブジェクト.メソッド名()」の形式でメソッドが実行されて、インスタンスオブジェクトが特定される場合のみです。

インスタンスメソッド内から、静的プロパティや静的メソッドにアクセスする場合

this.constructor.静的メソッドのように記述します。

class TestCls {
    static STATIC_PROP = "静的プロパティ";
    static staticMethod(){ return "静的メソッド"; }
    method(){
        console.log( this.constructor.STATIC_PROP  );
        console.log( this.constructor.staticMethod() );
    }  
}
const test = new TestCls;
test .method();
>静的プロパティ
>静的メソッド
静的メソッドや静的プロパティ内からは、this.constructor.~のような記述で、他の静的メソッドや静的プロパティにアクセスはできません。

ゲッターとセッター

クラスを記述していると、プロパティの値を取得・変更するときに特定の処理をあわせて実行したい場合があります。その場合は、ゲッター(ゲッターメソッド)、セッター(セッターメソッド)を使います。

構文:ゲッターとセッターの定義

class クラス名{
    get ゲットプロパティ(){
        return ゲットプロパティを参照した際に取得される値;
    }
    set セットプロパティ( 設定された値 ){
        プロパティに値を設定するときに実行したい処理
    }
}

構文:ゲッターとセッターの使用方法

const obj = new クラス名;
console.log( obj.ゲットプロパティ );  //ゲッターの実行
obj.セットプロパティ = 値;            //セッターの実行

ゲッター、セッターを実行するときは、メソッドの実行と違い末尾の()は必要ありません。オブジェクトのプロパティのように値の取得・変更を行えば、ゲッターメソッドやセッターメソッドが実行されるようになります。そのため、ゲットプロパティやセットプロパティは、他のプロパティ名と重複しないようにする必要があります。

使用例

class User {
    constructor( firstname, lastname){
        this.firstname = firstname;
        this.lastname = lastname;
    }
    get fullname(){
        return this.lastname + this.firstname;  //fullnameが取得された場合には氏名を結合して返す
    }
    set age( value ){
        this._age = Number( value );            //ageに値が設定された場合には_ageに数値を設定
    }
    get age(){
       return this._age;                        //ageが取得された場合には_ageプロパティの値を返す
    }
}
const taro = new Person( "太郎" , "山田" );

//ゲッターを通して値を取得
console.log( taro.fullname );      //※1
>山田太郎
//セッターを通して文字列で値を設定
taro.age = "18";           //※2
//ゲッターを通して値を取得
console.log( typeof taro.age );//taro._ageの値が返される
>number                 //数値型が取得される ※3
//オブジェクトの状態を確認
console.log( taro );
>{ firstname: "太郎", last name: "山田", _age: 18 } //_ageプロパティにageの値は保持

※1 fullnameゲッターの挙動

fullnameというゲッターを定義し、taro.fullnameと記述してプロパティのように取得しています。これによって、fullnameゲッターが実行され、その結果として戻り値(this.lastname + this.firstname)が返されます。

※2 ageセッターの挙動

ageセッターでは、代入演算子で設定された値(”18″)がセッターの引数(value)に渡されます。ageセッターでは、Numberによって文字列が数値に変換されて_ageプロパティに設定されます。そのため、taro.ageゲッターで取得した値は、数値型になります。

※3 _ageプロパティにageの値は保持

実際の値は_ageプロパティに保持しているため、オブジェクトのプロパティに_ageプロパティが追加されています。

アンダースコア(_)から始まるプロパティ
クラスのプロパティやメソッドの先頭にアンダースコア(_)を付けた場合には、そのプロパティやメソッドは「クラス内のみの使用に制限している(クラス外から参照してはいけない)」ことを表します。ただし、実際にはtaro._ageのように記述すれば値を取得・変更できます。ES6のクラスの記法では、クラスの外部からアクセス権限を制御する方法がないため、アンダースコアのような目印をつけて、他の開発者にクラス外から使用されないようにしています。

クラスの継承

クラスの継承とは、既存のクラスを継承する(引き継ぐ)ことで、既存のクラスの機能を利用して、少し機能の異なるクラスを新たに生成する記法です。これによって、既存のクラスのコードを再利用でき、冗長的な記述を減らすことができます。

クラスの継承の記法

//継承元クラス(親クラス)
class Parent{
    constructor( value ){
         this.parentProp = value;
    }
    parentMethod(){
        console.log( "親クラスのメソッド" ); 
    }
}
//継承先クラス(子クラス)
class Child extends Parent {         // extendsでParentクラスを継承  ※1
    constructor( parentProp, childProp ){
        super( parentProp );         //親クラスのコンストラクタを実行 ※2
        this.childProp = childProp;  //子クラス独自のプロパティ追加
    }
    childMethod(){
        //親クラスのプロパティを取得
        console.log( `子から親サクセス[ ${ this.parentProp } ]` );   ※3
    }
}
//子クラスからインスタンス化
const childObject = new Child("親","子");

//オブジェクトの確認
console.log( childObject ); 
>{ parentProp: "親", childProp: "子" }  //親クラスのプロパティも設定されている  ※4
childObject.parentMethod();             //親クラスのメソッドも実行可能!       ※4
>親クラスのメソッド
childObject.childMethod();              //子クラスのメソッドも実行可能!
>子から親にアクセス[ 親 ]
クラス継承のイメージ

※1 extendsでクラスを継承

クラスの継承の記法では、extendsキーワードを使ってクラスを継承します。上記の例では、ChildクラスがParentを継承しています。(Parentは継承元の親クラス、Childクラスは継承先の子クラスです)。なお、複数のクラスを継承することはできません。

※2 superキーワードで親クラスのコンストラクタを実行

子クラスにコンストラクタを記述する場合、必ずsuperをキーワードで親のコンストラクタを実行します。super( ・・・ )を呼び出さないとエラーなります。また、super( ・・・ )の実行の前にthisキーワードにアクセスした場合も、エラーが発生します。

※3親のプロパティの取得

thisキーワードを通して親クラスのプロパティやメソッドにアクセスできます。また、子クラスに親クラスと同じ名前のメソッドが存在する場合、superキーワードを通して親クラスのメソッドを指定することもできます。

superで親クラスのメソッドを実行

class Parent{
    method() { console.log( "親クラスのメソッド" ) }
}
class Child extends Parent {
   method() { console.log( "子クラスのメソッド" ) }
   myMethod(){ this.method() }                      //子クラスのmethodを実行
   parentMethod() { super.method() }                //親クラスのmethodを実行
}
const obj = new Child;
obj.myMethod();
>子クラスのメソッド
obj.parentMethod();
>親クラスのメソッド

※4親クラスのプロパティとメソッドが利用可能

Childクラスから作成したオブジェクト(childObject)には、親クラスのプロパティ(parentProp)が存在することを確認できます。また、childObject.parentMethod();のように、親クラスのメソッドを使うこともできます。

生成元クラスの確認

インスタンスの生成元クラスを確認するには、instanceof演算子を使います。

構文:instanceofの記法

let result = インスタンス instanceof クラス名;

インスタンスの生成元クラス名がinstanceofの右オペランドのクラス名と一致する場合にはtrueが返り、一致しない場合にはfalseが返ります。このクラス名の比較対象には、継承元のクラスも含まれます。つまり、次のような結果になります。

class Parent {  }
class Child extends Parent {  }
const obj = new Child;
console.log( obj instanceof Child );
>true
console.log( obj instanceof Parent );
>true

上記の例では、オブジェクト(obj)はChildクラスを使ってインスタンス化を行っています。また、ChildクラスはParentクラスを継承しているため、オブジェクトの生成元となったクラスをinstanceofで検査した場合、どちらの結果もtrueとなります。

すべてのクラスはObjectクラスを継承している

次のようにObjectに対して比較した場合も結果はtrueを返します。

console.log( obj instanceof Object);
>true

これは、ParentクラスがObjectクラスを継承していることを表しています。これは、JavaScriptでは明示的に継承元クラスを記述しない場合には、自動的にObjectクラスを継承するようになっています。つまり、は、Objectクラスは、すべてのクラスを継承しているクラスということになります。そのため、一部例外を除き、すべてのインスタンスオブジェクトは、Objectクラスのメソッドを使うことができます。例えば、ObjectクラスにはhasOwnPropertyというメソッドが定義されており、このメソッドを使うことで、オブジェクトがプロパティを保持しているか確認できます。

hasOwnPropertyによるプロパティの存在確認

class Person {
    constructor(){
        this.name = "山田太郎";
    }
}
const taro = new Person;
console.log( taro );
>{ name: "山田太郎" }
console.log( taro.hasOwnProperty( "name" ) ); //実装していないhasOwnPropertyメソッドが利用可能
>true

hasOwnPropertyメソッドとin演算子

hasOwnPropertyは自身のオブジェクトにプロパティが存在するかを確認するメソッドです。同じような機能を持つin演算子もあります。

構文:hasOwnPropertyの記法

let 真偽値 = オブジェクト.hasOwnProperty( "プロパティ" );

構文:in演算子の記法

let 真偽値 = "プロパティ名" in オブジェクト;

hasOwnPropertyメソッドとin演算子には、次のような違いがあります。

hasOwnPropertyがtrueを返す条件

hasOwnPropertyの結果がtrueになるのは、プロパティ名が自身のオブジェクトのプロパティとして存在する場合です。これには、継承したクラスのプロパティも含まれています。一方、メソッド名と一致してもtrueにはなりません。

in演算子がtrueを返す条件

in演算子の結果がtrueになるのは、オブジェクトが保持するプロパティまたはメソッドと一致した場合です。これには、継承したクラスのプロパティやメソッドも含まれます。

そのため、プロパティのみを確認する場合はhasOwnPropertyを使い、メソッドまで確認する場合は、in演算子を使います。

class Person{
    constructor(){
        this.name = "山田太郎";
    }
    hello(){
        console.log("こんにちは");
    }
}
const taro = new Person;
console.log( taro.hasOwnProperty( "name" ) );    //hasOwnPropertyメソッドを使ったプロパティの存在確認
>true
console.log( "name" in taro );                   //in演算子を使ったプロパティの存在確認
>true
console.log( taro.hasOwnProperty( "hello" ) );   //hasOwnPropertyメソッドを使ったプロパティの存在確認
>false
console.log( "hello" in taro );                  //in演算子を使ったメソッドの存在確認
>true

また、Objectクラスは自動的に継承されるため、in演算子でObjectクラスのメソッドを確認した場合にはtrueが返されます。

Objectクラスのメソッドの存在確認

console.log( "hasOwnProperty" in taro );
>true

ES2022でのクラス記法

ES2022のバージョンから、クラスの機能が拡張されています。ブラウザによっては、まだ機能が実装されていない可能性もあるため、実際の環境で使うためには、Babelやwebpackといった、古いブラウザでも動くコードに変換してくれるソフトウェアが必要になる場合があります。

コンストラクタの省略

ES6のクラス記法では、プロパティを設定する場合には、必ずコンストラクタ内で行う必要があります。しかし、ES2022のクラスの記法では、クラスのトップレベルでプロパティを宣言できるようになりました。これにより、プロパティに値を設定するだけであれば、コンストラクタを省略できます。

//ES2015のクラスで書いた場合
class ES2015 {
    constructor(){       //コンストラクタ内でプロパティを設定
        this.prop = 0;
    }
}
//ES2022のクラスで書いた場合
class ES2022 {  
    prop = 0;           //クラスのトップレベルでプロパティを設定可能
}

なお、コンストラクタの引数を受け取ってプロパティに設定した場合は、ES2015のクラスと同様に、コンストラクタ内で初期値を設定する必要があります。

コンストラクタの引数をプロパティに設定する場合

class ES2022 {
    prop1 = 0;   //コンストラクタの引数を代入しない場合
    constructor( arg ){       //コンストラクタ内でプロパティを設定
        this.prop2 = arg;      //コンストラクタの引数を代入する場合、constructor内で行う
    }
}

プライベートなアクセス権の追加

ES2022のクラスの記法では、自クラス内からのみアクセス可能なプロパティやメソッドを定義できるようになりました。そのため、ES2022のクラスでは、パブリックとプライベートの2種類のアクセス権を使い分けることができます。

パブリックなプロパティやメソッド

パブリックは、クラスの外から出もプロパティやメソッドにアクセス可能な状態を表します(パブリックプロパティ、パブリックメソッドと呼びます)。なお、これまで扱ってきたクラスのプロパティやメソッドは、このパブリックに分類されます。

プライベートなプロパティやメソッド

プライベートは、自クラス内からのみアクセス可能な状態を表します。このプロパティやメソッドの定義方法がES2022から追加された仕様です(プライベートプロパティ、プライベートパブリックメソッドと呼びます)。プライベートプロパティやプライベートメソッドを定義する場合は、先頭に#を付けます。

class Counter{
    #count = 0;                     //プライベートプロパティ
    #print(){                       //プライベートメソッド
        console.log( this.#count );
    }
    increment(){                    //パブリックメソッド
        this.#count++;
        this.#print();              //プライベートメソッドを実行
    }
}
const counter = new Counter;
counter.increment();                //プライベートメソッドを実行
>1
counter.#count = 10;                //プライベートプロパティはクラス外からアクセス不可
counter.#print();                   //プライベートメソッドはクラス外からの実行不可
プライベートプロパティを使うときは、クラスのトップレベルで宣言する必要がある