JavaScript
2022.10.13
JavaScriptのclass構文をマスター!使い方・書き方まとめ
2023.11.18

JavaScriptを利用した開発において、大規模な開発になると必要になってくるのが「オブジェクト指向」という考え方です。


JavaScriptにもクラスの概念が登場し、オブジェクト指向プログラミングが可能となりました。
方を解説します。



1. JavaScriptのclass構文とは?

JavaやC#といったオブジェクト指向言語の多くには、「クラス」という概念があります。

クラスは、オブジェクトの設計書と言える存在で、オブジェクトはクラスをもとに「インスタンス」を生成して利用します。

JavaScriptにもクラスの概念が存在し、class構文を利用することでオブジェクト指向プログラミングが可能です。

①メソッドやコンストラクタを簡単に定義できるもの

クラス内のメソッドやコンストラクタを容易に定義することで、JavaScript上でオブジェクト指向的なプログラミングが可能となります。

JavaScriptのclass構文は、Javaのclass構文に似ているため、Javaを学習した人であれば比較的容易に習得できることもメリットと言えるでしょう。

②ES2015(ES6)から導入された

JavaScriptは関数型のプログラミング言語であるため、オブジェクト指向を表現するために利用するclass構文は存在しません。

という話が、2015年までの常識でした。

しかしながら、ES2015の仕様ではじめてclass構文が導入されました。

ES2015はほとんどのブラウザが対応していますが、IE11のような古いブラウザでは対応していません。

2022年時点でIE11のサポートは終了いますが、IEモードで動作するような社内システムを開発する場合などには注意が必要です。

ES2015の「ES」とは?

余談になりますが、ES2015は「ES6」とも呼ばれています。

この「ES」とは何なのでしょうか?

実は、「ES」はECMA Scriptの略で、JavaScriptのベースとなる言語仕様を指します。

つまり、「ES6」とはECMA Scriptの第6版であることを表しているのです。

正式なリリースの2015年にあわせて「ES2015」としてリリースされましたが、それよりも前は「ES6」と表現されていました。

つまり、「ES6」と「ES2015」は同じことを指しています。

2015年より前からJavaScriptを利用しているフロントエンドエンジニアの中では「ES6」として認知されていますので、混乱しないように注意しましょう。

ちなみに、次の第7版は「ES2017」と呼ばれており、後述するような新機能も追加されています。



2. class構文とfunctionでのクラスの違い


それでは、従来の構文とclass構文の違いを見ていきましょう。

①ES2015(ES6)以前はfunctionでクラスを表現

ES2015以前ではclass構文が存在しないため、functionを用いてクラスを表現していました。

function TestClass(param1, param2) {
  // コンストラクタ相当
  this.field1 = param1;
  this.field2 = param2;
}

// TestClassにメソッドを定義
TestClass.prototype.method1 = function() {
  console.log(this.field1);
}

これを使用する場合には、以下のように記述します。

const instance = new TestClass('param1', 'param2');
instance.method1(); //-> ‘param1'と出力される

「new」を利用してインスタンスを生成する点では、Javaなどのプログラミング言語と同じです。

②class構文で定義したクラスは関数として呼び出せない

ES6以前のクラスは関数として定義していたため、通常の関数として呼び出してもエラーにはなりませんでした。

const instance = TestClass('param1', 'param2'); // ここではエラーにならない
instance.method1(); // エラー「Cannot read properties of undefined (reading 'method1') 」が発生

一方で、class構文を利用した場合、関数として呼び出すことができません。

class TestClass {
    // ~処理は省略
}

const instance = TestClass(); 
// -> エラー「Class constructor TestClass cannot be invoked without 'new」が発生

どちらも、インスタンス生成時に「new」をつけ忘れたことによることが原因ですが、関数としてクラスを実装した場合には予期しない場所でエラーが発生してしまうことがわかります。

一方で、class構文で記述することで正しい場所でエラーが発生します。

正しくエラーを認識するためにも、class構文を利用してクラスを作成するようにしましょう。



3. クラスの作り方


それでは、class構文を利用したクラスの作り方を解説します。

①基本構文

まずは、classの基本構文から紹介します。

クラス宣言

class Messenger {
  constructor(user, message) {
    this.user = user;
    this.message = message;
  }
}


class式

もうひとつの構文が、class式と呼ばれる方式です。

class式には、無名方式と名前付き宣言の2種類が存在します。

この2種類では使用方法に大きな違いはありませんが、すこし違いがあるので見てみましょう。

「クラス名.name」を使用することでそのクラスのクラス名を取得できるのですが、出力される内容に差が出ます。

// 無名方式でのクラス宣言
let Messenger = class {
  constructor(user, message) {
    this.user = user;
    this.message = message;
  }
}

// クラス名を出力
console.log(Messenger.name); // 「Messenger」と出力される

// 名前付き方式でのクラス宣言
Messenger = class Messenger2 {
  constructor(user, message) {
    this.user = user;
    this.message = message;
  }
}

// クラス名を出力
console.log(Messenger.name); // 「Messenger2」と出力される

無名方式でクラスを宣言した場合には、letとして宣言した変数名がクラス名として扱われます。

一方で、名前付きでクラスを宣言した場合には、変数名ではなくクラス名の方がクラス名として取得されるのです。

これらの方式で機能面の違いはほぼありませんが、細かい違いがあるということは覚えておくと良いでしょう。

②コンストラクタを定義

次に、クラスのコンストラクタ定義を紹介します。

コンストラクタは「constructor」を使用した関数を定義することで、その関数がコンストラクタとして定義されます。

先ほどのMessengerクラスには、引数をふたつもつコンストラクタを定義しました。

コンストラクタでは、「this.user」と「this.message」のふたつのインスタンス変数に値を格納しています。

class Messenger {
  constructor(user, message) {
    this.user = user;
    this.message = message;
  }
}

③フィールドを定義

次に、クラスのフィールドを定義しましょう。

先ほどのクラスは、以下のように書き換えられます。

class Messenger {

  user;
  message;
  
  constructor(user, message) {
    this.user = user;
    this.message = message;
  }
}

JavaScriptの特性上、コンストラクタで「this.変数名」としたものは自動的にフィールド変数として扱われます。

そのため、フィールドを別途宣言する必要性は薄いとも思えるでしょう。

しかしながら、コンストラクタ内でのみ宣言されたフィールドは見落とすことも多く、多人数で開発する場合には予期しないバグの温床になりかねません。

外部からアクセスするようなフィールドは、明示的にフィールドとして定義するようにしましょう。

④メソッドを定義

フィールドを定義したら、メソッドを定義していきましょう。

メソッドは、クラスの中に関数と同じように定義します。

class Messenger {

  user;
  message;
  
  constructor(user, message) {
    this.user = user;
    this.message = message;
  }

  // メッセージの表示
  showMessage() {
    console.log(`${this.user}, ${this.message}`);
  }
}

メソッドからフィールドへアクセスする場合には、コンストラクタと同様に「this.変数名」とすることで値を参照できます。



4. クラスの使い方


クラスの作成方法を理解したところで、次は作成したクラスを利用してみましょう。

①オブジェクトを作成

まずは、クラスのオブジェクトを作成します。

オブジェクトを作成するには、「new」を使用します。

先ほどのMessengerクラスのインスタンスを生成してみましょう。

const msg = new Messenger('taro', 'hello');

②プロパティの取得・設定

次に、プロパティ(インスタンスのフィールド変数)を取得してみましょう。

プロパティを取得する場合には、「インスタンス変数名.プロパティ名」で取得できます。

// msgインスタンスの「user」を取得
console.log(msg.user); // -> taro が出力される

③メソッドの呼び出し

今度は、インスタンスのメソッドを呼び出してみましょう。

クラスのメソッドはJavaScriptの関数として定義されているので、通常の関数呼び出しと同様に記述しますが、
プロパティ同様に「インスタンス変数名.メソッド名」とする必要があります。

msg.showMessage(); // -> taro, hello が出力される




5. 一歩進んだクラスの使い方

クラスの作成や使い方をマスターしたら、さらに進んだ使い方を学びましょう。

ここでは、よりオブジェクト指向を意識したプログラミングをするための機能を紹介します。

オブジェクト指向の詳しい説明は下記のページを参照してみてください。


ここで紹介するclass構文には、ES2015よりもさらに新しいバージョンで制定された内容を含みます。


ほとんどのPCブラウザでは正常に動作しますが、AndroidやiOSなどの古い OSバージョンを利用している場合には、動作しないことがあるため注意しましょう。

①staticフィールド・メソッド

通常、フィールドやメソッドは生成されたオブジェクトインスタンスごとに動作しますが、staticフィールドやstaticメソッドは、そのクラスのオブジェクト全てに対して作用します。

staticフィールドは、インスタンス名ではなくクラス名を利用してアクセスします。

class Messenger {

  // static宣言したフィールド
  static defaultMessage = 'Hello, No name!';
  
  user;
  message;
  
  constructor(user, message) {
    this.user = user;
    this.message = message;
  }

  // メッセージの表示
  showMessage() {
    console.log(`${this.user}, ${this.message}`);
  }

  // static宣言したメソッド
  static showMessage() {
    console.log(Messenger.defaultMessage);
  }
}

// インスタンスのアクセス
const msg = new Messenger('taro', 'hello');
msg.showMessage(); // taro, hello が出力される

// staticアクセス
Messenger.showMessage(); // Hello, No name! が出力される

このように、同じメソッドであってもstaticの有無で異なるメソッドを定義することもできます。

ただし、staticメソッドから非staticフィールドまたはメソッドへアクセスできないことに注意しましょう。

class Messenger {
  user;
  // ~省略
  static showMessage() {
    console.log(this.user); // undefinedとなる
  }
}

②privateフィールド・メソッド

privateは、その名の通り「隠蔽された」という意味を持ちます。privateとして宣言されたフィールドやメソッドは同一クラス内からのみ参照でき、クラスの外からはアクセスできません。

privateフィールド・メソッドの機能は、ES2017以降に実験的に導入され、2022年制定のES2022で正式に採用された、新しい記法です。

privateとして宣言するフィールドまたはメソッド名は「#」で始めます。

class Messenger {
  
  #user;
  #message;
  
  // コンストラクタ
  constructor(user, message) {
    this.#user = user;
    this.#message = message;
  }

  // メッセージの表示
  showMessage() {
    console.log(this.#createMessage());
  }
    
  // メッセージを生成するprivateメソッド
  #createMessage() {
    return `${this.#user}, ${this.#message}`;
  }
}

// インスタンスのアクセス
const msg = new Messenger('taro', 'hello');
msg.showMessage(); // taro, hello と出力される

console.log(`instance field is ${msg.user}`); // instance field is undefined と出力される
console.log(msg.createMessage()); // エラー「Private field '#createMessage' must be declared in an enclosing class」が発生する

このように、privateとして宣言したフィールドまたはメソッドへは、アクセスできないということを覚えておきましょう。

③getter・setter

オブジェクト指向の概念として「カプセル化」が存在しますが、それを実現するのが「getter(ゲッター)」と「setter(セッター)」です。

JavaScriptでも、privateフィールドとpublicメソッドを組み合わせることで、getterとsetterを実現できます。

先ほどのMessengerクラスにgetterとsetterを設定してみましょう。

class Messenger {
  
  #user;
  #message;
  
  // userのgetter
  get user() {
    return this.#user;
  }
  
  // messageのgetter
  get message() {
    return this.#message;
  }
  
  // messageのsetter
  set message(arg) {
    this.#message = arg;
  }
  
  // コンストラクタ
  constructor(user, message) {
    this.#user = user;
    this.#message = message;
  }

  // メッセージの表示
  showMessage() {
    console.log(this.#createMessage());
  }
    
  // メッセージを生成するprivateメソッド
  #createMessage() {
    return `${this.#user}, ${this.#message}`;
  }
}

// インスタンスのアクセス
const msg = new Messenger('taro', 'hello');

msg.showMessage(); // taro, hello と出力される

msg.user = 'jiro';
msg.message = 'こんにちは!';

msg.showMessage(); // taro, こんにちは! と出力される

このとき、userにはgetterのみ、messageにはgetterとsetterの両方を設定していることに着目します。

Messengerクラスには、フィールドuserのsetterは存在しないため、以下の処理ではフィールド変数を更新しません。

msg.user = 'jiro';

結果として、userの値は更新されずにmessageの内容のみ更新されます。

このように、getterとsetterを活用することで、クラスの外部から不用意に値が変更されるのを防げるのです。



6. クラスの継承


オブジェクト指向のひとつの概念として、クラスの継承と抽象化が存在します。

JavaScriptのクラスも継承を利用できますので、より高度なプログラミングを実現できます。

class Messenger {
  user;
  message;
  // コンストラクタ
  constructor(user, message) {
    this.user = user;
    this.message = message;
  }
  // メッセージの表示
  showMessage() {
    console.log(`${this.user}, ${this.message}`);
  }
}

class MessengerEx extends Messenger {
  #honor;
  // コンストラクタ
  constructor(user, honor, message) {
    // super()を利用して親クラスのコンストラクタを呼び出し
    super(user, message);
    this.#honor = honor;
  }
  // 同名メソッドを宣言すると上書きされる
  showMessage() {
    console.log(`${this.#honor}.${this.user}, ${this.message}`);
  }
}

// インスタンスのアクセス
const msg = new MessengerEx('taro', 'Mr', 'hello');
msg.showMessage(); // Mr.taro, hello と出力される

このように、「this.フィールド名」を使用することで親クラスのフィールドへアクセスできます。

ただし、privateフィールドは継承先となる子クラスからも参照できないことに注意が必要です。


子クラスからアクセスされる可能性のあるフィールドやメソッドには「#」を付けないようにする、と覚えましょう。



7. class構文を使う際の注意点


最後に、JavaScriptでクラスを利用する場合の注意点を紹介します。

①クラス名は大文字ではじめるのが慣習

class構文を利用する際には、クラス名を大文字で開始するようにしましょう。

前述の通り、JavaScript上ではクラスもfunctionと同じような扱いをします。

そのため、一見するとクラスとして利用するのか、それとも通常の関数なのかわからなくなることがあります。

「これはクラスだよ」という判断をしやすくする目的でも、クラス名は明示的に大文字から開始するようにしましょう。

②thisの扱いに注意しよう

JavaScriptのクラスでは、「this」を用いてインスタンス変数へアクセスできます。この際、thisが思いもよらないものを参照する可能性があるので注意しましょう。

その例を紹介します。

失敗する例

JavaScriptは、一度メソッドを変数として格納し、あとから実行することが可能です。

そのため、以下のように一度「msg.showMessage」を変数に格納し、あとから実行できるのです。

class Messenger {
  
  // コンストラクタ
  constructor(user, message) {
    this.user = user;
    this.message = message;
  }
  // メッセージの表示
  showMessage() {
    console.log(`${this.user}, ${this.message}`);
  }
}

const msg = new Messenger('taro', 'hello');

const show = msg.showMessage;
show(); // エラー「Cannot read properties of undefined (reading 'user') 」が発生する

このとき、showMessage内で「this.user」を参照できずにエラーとなっていることがわかります。

これは、JavaScriptの「this」の仕様によるもので、メソッドを変数に格納した場合、そのメソッドの「this」は「msg」インスタンスではなく、「show」の変数が属しているオブジェクト(この場合はwindowオブジェクト)となってしまうのです。

このように、JavaScriptにおける「this」は非常に面倒な存在ですので、利用する場合には注意しましょう。

書き換え例

このJavaScriptのthis問題を解決する方法のひとつとして、「アロー関数」を利用する方法があります。

showMessageをアロー関数化した「showMessageArr」を作り、同じことを実施してみましょう。

class Messenger {
  
  // コンストラクタ
  constructor(user, message) {
    this.user = user;
    this.message = message;
  }
  // メッセージの表示
  showMessage() {
    console.log(`${this.user}, ${this.message}`);
  }
  // アロー関数を用いたメッセージの表示
  showMessageArr = () => {
    console.log(`${this.user}, ${this.message}`);
  }
}

const msg = new Messenger('taro', 'hello');

const show = msg.showMessageArr;
show(); // taro, hello と出力される

今度は、正常に処理が実行されました。

実は、この仕様は前述した「functionによるclass構文」という、古い仕様が関係しています。

thisは自身のオブジェクトを指すのですが、JavaScriptでは関数をひとつのオブジェクトとして捉えます。

一方で、アロー関数はこの制限が存在しないため、メソッドを別で定義されても正しい「this」を参照できるのです。

長くなってしまうため詳しい話は割愛しますが、「JavaScriptではthisの取り扱いに注意すること」とだけ、まずは覚えておきましょう。


※掲載された社名、製品名は、各社の商標及び登録商標です。

この記事をシェア