Michi's Tech Blog

一人前のWebエンジニアを目指して

JavaScriptのオブジェクト指向を完全に理解する

こんにちは!
スマレジ テックファームのMichiです!

今回の記事は「JavaScriptを完全に理解する」シリーズのオブジェクト指向編です。

JavaScriptのオブジェクトとは?

他のオブジェクト指向言語では、「オブジェクト=クラス(から生成されるインスタンス)」のことを指します。 一方、JavaScriptでオブジェクトというと、{ key: value }のようないわゆる連想配列を思い浮かべる方が多いと思います。
実はJavaScriptでは、連想配列とオブジェクトは同一のもの」であり、その時々の使い方や文脈によって言い方を変えているに過ぎません。
そのため、本来のJavaScriptにクラスの概念は存在しないのですが、ES2015からはクラス構文が導入され、他のクラスベースのオブジェクト指向言語と同じような感覚で書けるようになりました。(ちなみに、クラスベースのオブジェクト指向に対して、JavaScriptオブジェクト指向はプロトタイプベースと呼ばれるらしい。)

本記事ではES2015に倣い、クラス構文でのオブジェクト指向を解説します。

クラスを定義する

前述のように、ES2015からは他の言語と同じように、class クラス名でクラスを定義することができるようになりました。
インスタンス変数はconstructor関数の中で定義します。尚、この関数名はconstructorで固定です。

class Member {
  // コンストラクター
  constructor(lastName, firstName) {
    this.lastName = lastName;
    this.firstName = firstName;
  }

  // メソッド
  getName() {
    return this.lastName + ' ' + this.firstName;
  }
}

const member = new Member('山田', '太郎');
console.log(member.getName()); // 結果:山田 太郎

ゲッターとセッター

get構文を使用することで、読み取り専用のプロパティ(ゲッター)を定義できます。
先ほどのgetName関数をゲッターを使って定義したものが下のコードになります。

class Member {
  // コンストラクター
  constructor(lastName, firstName) {
    this.lastName = lastName;
    this.firstName = firstName;
  }

  // ゲッター
  get fullName() {
    return this.lastName + ' ' + this.firstName;
  }
}

const member = new Member('山田', '太郎');
console.log(member.fullName); // 結果:山田 太郎

尚、ゲッターで定義したプロパティは、下記のように代入して上書きすることはできません。

member.fullName = '佐藤 次郎';
console.log(member.fullName); // 結果:山田 太郎

プロパティを上書きするためにはset構文を使用して、セッターを定義します。

class Member {
  // コンストラクター
  constructor(lastName, firstName) {
    this.lastName = lastName;
    this.firstName = firstName;
  }

  // ゲッター
  get fullName() {
    return this.lastName + ' ' + this.firstName;
  }

 // セッター
  set fullName(value) {
    [this.lastName, this.firstName] = value.split(" ");
  }
}

const member = new Member('太郎', '山田');
member.fullName = '佐藤 次郎';
console.log(member.fullName); // 結果:佐藤 次郎

静的プロパティ/メソッドを定義する

staticキーワードをプロパティ/メソッド宣言の頭に付けることで、静的プロパティ/メソッドを定義することができます。
静的プロパティ/メソッドは、「インスタンス化されていないクラスから直接呼び出せるプロパティ/メソッド」です。

class Area {
  static PI = 3.14;
  static getCircle(radius) {
    return radius ** 2 * this.PI;
  }
}

// インスタンス化せずに、クラスから直接呼び出している
console.log(Area.PI) // 結果:3.14
console.log(Area.getCircle(2)) // 結果:12.56

サンプルのように、同じクラス内の静的メソッドまたはプロパティを静的メソッドから呼び出すには、 thisキーワードを使います。

既存のクラスを継承する

既存のクラスを継承する場合は、extendsキーワードを利用します。

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

  getName() {
    return this.lastName + " " + this.firstName;
  }
}

// 子クラス(Memberクラスを継承)
class BusinessMember extends Member {
  work() {
    return this.getName() + "は働いています。";
  }
}

const businessMember = new BusinessMember('山田', '太郎');
console.log(businessMember.getName()); // 結果:山田 太郎
console.log(businessMember.work()); // 結果:山田 太郎は働いています。

親クラスのメソッドをオーバーライドする

親クラスで定義されたメソッド/コンストラクターは、子クラスで上書き(オーバーライド)することもできます。

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

  getName() {
    return this.lastName + " " + this.firstName;
  }
}

// 子クラス(Memberクラスを継承)
class BusinessMember extends Member {
  // 引数にclazzを追加
  constructor(lastName, firstName, clazz) {
    super(lastName, firstName);
    this.clazz = clazz;
  }

  // 役職込みの名前を返すよう修正
  getName() {
    return super.getName() + '/役職:' + this.clazz;
  }
}

const businessMember = new BusinessMember('山田', '太郎', '課長');
console.log(businessMember.getName()); // 結果:山田 太郎/役職:課長

まとめ

JavaScirptのオブジェクト指向について解説しました。
JavaScriptオブジェクト指向は特殊でとっつきにくいイメージがありましたが、ES2015で実装されたクラス構文を使用すれば、他の言語と同じ感覚で書けるので、大きな混乱もなく理解することができたと思います。