Javascript

【JavaScript】ES6のクラスの定義方法

こんにちは、ともです。

JavaScriptを勉強していましてES6のclass構文を勉強し始めました。

JavaScriptはプロトタイプベースのオブジェクト指向言語であり、オブジェクトをプロトタイプチェーンで繋げることで継承の関係を作成しますね。

ES6からはclass構文が導入され、C++やJavaをやっていた方が理解しやすいclassの定義方法(実際はオブジェクトだが)が導入されました。

そこでES5からES6へ対応するためにclass構文について理解をまとめておきたいと思います。

class構文でクラスを作成してみる

ES6のクラス(オブジェクトだけど)

class Member {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

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

class命令を利用することによりclassを定義することができます。C++やJavaから入った僕には凄く理解しやすいです。

constructorがコンストラクタを表している訳ですね。JavaScriptはprivateやprotectというキーワードが無く全てpublicとなります。

よってgetName()と定義したメソッドもpublicとなり、どこからでもこのメソッドにアクセスすることができます。

ES5でのクラス

function Member(firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
}

Member.prototype.getName = function () {
  return this.lastName + this.firstName;
}

プロトタイプでメソッドを定義することで暗黙的な参照を行わせるES5時代の書き方です。

ES6からのコンストラクタやメソッドの定義方法に比べて理解しずらいですね。ただ、プロトタイプに慣れていればそれほど難しいことが書いている訳でもありません。

ES6のclassはES5のプロトタイプなどによる継承を分かりやすくした糖衣構文(シンタックスシュガー)であるためES5のオブジェクトを理解することは大切ですね。

コンストラクタを強制的に実行できない

let m = new Member('太郎', '山田');// 可能
Member('太郎', '田中');// 不可能

ES5でのクラスの定義方法では単に関数オブジェクトとして定義していたため、コンストラクタに当たる処理をメソッドとして実行することが可能でした。

しかしclass構文により定義したクラスでは

Uncaught TypeError: Class constructor Member cannot be invoked without ‘new’

というTypeErrorとなります。『newを利用せずに呼び出すな!』という意味です。

定義前クラスを呼び出せない

let m = new Member('太郎', '山田');

class Member {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

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

class構文を利用した場合、定義前のクラス(オブジェクト)からインスタンスを生成できません。

Uncaught ReferenceError: Member is not defined

Memberオブジェクトはまだ定義されていませんとなります。

ES5での定義方法はfunction命令により静的に定義されたものなのでオブジェクト定義前にインスタンスを生成することが可能でした。

classを定義してからインスタンスを生成する方が一般的ですし馴染みやすいです。

変数の定義方法

class Member {
  this.hoge = 1; // こんなことはできない。 

  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

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

}

他の言語を学習した方ならクラス内で場所を気にせず変数を定義していたと思いますが、JavaScriptではそのようなことはできません。

JavaScriptでは

  • コンストラクタ内で『 this. 』でプロパティを定義
  • get/set構文を使ってプロパティを定義

の2種類があります。

get/set構文によるプロパティの定義

class Member {
  get hoge() {
    return this._hoge;
  }

  set hoge(val) {
    this._hoge = val;
  }
}

var m = new Member();
m.hoge = 'OK'; // set hogeの実行
console.log(m.hoge);// get hogeの実行
  • getプレフィックスが付いたメソッドは変数の値を取得するときに実行
  • setプレフィックスが付いたメソッドは変数に値を代入するときに実行

上記のルールでこのメソッドは実行されます。

JavaScriptのObjectはこのようなget/setプレフィックスを利用できることを理解していれば理解が進むと思います。

静的なメソッドの定義(static)

他の言語でもクラスメソッドをstatickキーワードで定義できるようにES6のクラス構文statickキーワードを利用できます。

class Area {
  static getArea(base, height) {
    return base * height / 2;
  }
}

console.log(Area.getArea(10, 2));

ClassName.StaticMethod()という『ドット』でアクセスすることが可能です。

継承(extends)

class Member {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

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

class BusinessMember extends Member {
  work() {
    return this.getName() + 'は働いています。';
  }
}

var m = new BusinessMember('田中', '太郎');
console.log(m.work()); // 田中太郎は働いています。

extendsキーワードによってMemberを継承し、継承元のgetName()を呼び出せていることが分かります。

オーバーライド

class Member {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

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

  work() {
    console.log('働きます!');
  }
}

class BusinessMember extends Member {
  work() {
    return this.getName() + 'は働いています。';
  }
}

var m = new BusinessMember('田中', '太郎');
console.log(m.work());// 田中太郎は働きます!

BusinessMemberオブジェクトでオーバーライドしました。

オーバーライドとは基底クラスで定義してメソッドやコンストラクタを派生クラスで上書きすることです。

Memberクラス(基底クラス)、BusinessMemberクラス(派生クラス)の両方にworkメソッドが存在しますが、派生クラスのworkメソッドが動いており上書きできています。

基底クラスのメソッドを実行(super)

基底クラスのコンストラクタやメソッドを実行するにはsuperキーワードが利用できます。

class Member {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

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

class BusinessMember extends Member {
  constructor(firstName, lastName, position) {
    super(firstName, lastName);//super(firstName, lastName)でMemberのコンストラクタを実行
    this.position = position;
  }
  getName() {
    return super.getName() + '/役職:' + this.position;//super.getName()でMemberのgetNameを実行
  }
}

var m = new BusinessMember('田中', '太郎', '課長');// 田中太郎/役職:課長
console.log(m.getName());

superキーワードを利用することで基底クラスのメソッドやコンストラクタを実行することができました。

  • super(args, ...)で基底クラスのコンストラクタを実行
  • super.method(args, ...)で基底クラスのメソッドを実行

コンストラクタ内でsuperキーワードを実行する場合は先頭の文でなければなりません。

constructor(firstName, lastName, position) {
    this.position = position;
    super(firstName, lastName); // 先頭ではない
  }

上記のようにしてしまうと

Uncaught ReferenceError: Must call super constructor in derived class before accessing ‘this’ or returning from derived constructor

多重継承はできない

JavaScriptは多重継承をサポートしていません。プロトタイプベースのオブジェクト指向であるため1つの基底クラスしかもつことができないのです。

そのための解決方法としてMix-inというものがあります。

プライベートメンバー

Symbolを利用することによりプライベートメンバーを利用することができます。

const SECRET_PROPERTY = Symbol();

class Member {
  constructor(secret) {
    this.hoge = 'hoge'
    this.fuga = 'fuga'
    this[SECRET_PROPERTY] = secret;
  }

  getSecretValue() {
    return this[SECRET_PROPERTY]
  }
}

let m = new Member('secret')

for (var key in m) {
  console.log(key);
}

console.log(m.getSecretValue());//secret

プロパティを確認したところSymbolで生成したプロパティには『SECRET_PROPERTY』は現れません。クラス内からはアクセスが可能です。

まとめ

ES6のクラスの定義方法について理解をまとめました。

ES5のプロトタイプベースのオブジェクト指向であることは変わりませんがかなり書きやすいですね。他のオブジェクト指向言語と同じような感覚で書くことができました。