Javascript

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

こんにちは、ともです。

JavaScriptを絶賛勉強中です。ES2015以前とES2015以降ではクラスの定義方法に変化があったということを知りました。

この記事ではまずES2015以前のクラスの定義方法について理解をまとめたいと思います。

この記事の参考にしてものは

ES2015以前のクラスの定義方法

ES2015からclass構文が利用できるようになり、他の言語のようにクラスを定義できるようになりました。

ではES5ではどのようにclassを定義していたのでしょうか。

class構文は無くても、JavaScriptはオブジェクト指向言語であるためをクラスを定義することはできまします。

// ES2015以前
var Member = function (firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
  this.getName = function () {
    return this.lastName + '' + this.firstName;
  }
  console.log('コンストラクタの実行');
};

var mem = new Member('山田', '太郎');
console.log(mem.getName());
// Output:'コンストラクタの実行'
//         '山田太郎'

Memberクラスを定義するために関数リテラルを代入しています。

この関数リテラル自体がコンストラクタに相当しますので、new Member('山田', '太郎');を実行することでconsole.log('コンストラクタの実行');が呼び出されます。

JavaScriptは関数がデータ型として存在しているため、メソッドの定義も変数に格納する形で行われています。

newによりMemberコンストラクタが実行されたとき、thisキーワードはnewで生成されるインスタンスを参照します。(今回の場合はmem)

これによりthisキーワードによりクラス(オブジェクト)内の変数の定義が可能となります。

プロトタイプベースのオブジェクト指向

JavaScriptはプロトタイプベースのオブジェクト指向です。

JavaやC++では同一のクラスを元に生成されたインスタンスは全て同じ関数やプロパティを持ちます。

JavaScriptでは同一のクラスから生成されたインスタンスであっても全て同じ関数やプロパティを持っているとは限りません。

次のコードを見てみます。

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

var mem1 = new Member('山田', '太郎');
mem1.getName = this.getName = function () {
  return this.lastName + '' + this.firstName;
};

console.log(mem1.getName());//Output:山田太郎

var mem2 = new Member('田中', '二郎');
console.log(mem2.getName());// mem2.getName is not a function

mem1とmem2というインスタンスを生成しました。

mem1には動的に名前を出力する関数を定義しました。

これにより、

mem1はgetName()を持っており、mem2はgetName()を持っていない

という状況が発生します。

これはJavaやC++を書いたことがある人なら違和感を持ちますがプロトタイプ(雛形)ベースのオブジェクト指向であるJavaScriptであればありえます。

JavaやC++に比べてクラスの縛りが緩く、JavaScriptのクラスの定義はベースとなるインスタンスを生成するだけで追加なども可能であると考えると理解が捗りました。

コンストラクターが呼び出し可能

// ES2015以前
var Member = function (firstName, lastName) {
  this.firstName = firstName;
  this.lastName = lastName;
};

var mem = Member('山田', '太郎');// new Member('山田', '太郎')
console.log(firstName);// global object
console.log(lastName);// global object

こちらの記述方法だと関数がコンストラクターの役割を担うため、関数としても呼び出しが可能となります。

new Member('山田', '太郎')では無く、Member('山田', '太郎')という関数として呼び出すことが可能になる訳ですね。

この場合、 Memberクラス内のfirstNamelastNameがグローバルオブジェクトとして定義されます。

これはthisの参照先が関数内の変数はグローバルオブジェクトを参照し、Strictモードの場合はundefinedとなるためです。

コンストラクターの強制呼び出しへの対処

var Member = function (firstName, lastName) {
  if (!(this instanceof Member)) {
    return new Member(firstName, lastName)
  }
  this.firstName = firstName;
  this.lastName = lastName;
};

var mem = Member('山田', '太郎');// new Member('山田', '太郎')
console.log(firstName);// Reference Error
console.log(lastName);// Reference Error

このように関数としてMemberが呼び出された場合、thisはMemberのインスタンスでは無くグローバルオブジェクトとして扱われます。

そこでthisがMemberのインスタンスを参照しない場合、Memberのインスタンスを返すようにすることで対処が可能です。

この対処によりfirstNamelastNameは関数として呼ばれることはなく、つまりグローバル変数として定義されず、Reference Errorとなってくれます。

まとめ

ES2015以前のクラスの定義方法について勉強をまとめました。

  • 関数リテラルを利用して定義する
  • プロトタイプベースなのでインスタンス毎にカスタマイズ可能
  • コンストラクタの強制呼び出しの対処が必要である

以上について理解を深めることができました。