こんにちは、ともです。
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クラス内のfirstName
とlastName
がグローバルオブジェクトとして定義されます。
これは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のインスタンスを返すようにすることで対処が可能です。
この対処によりfirstName
とlastName
は関数として呼ばれることはなく、つまりグローバル変数として定義されず、Reference Error
となってくれます。
まとめ
ES2015以前のクラスの定義方法について勉強をまとめました。
- 関数リテラルを利用して定義する
- プロトタイプベースなのでインスタンス毎にカスタマイズ可能
- コンストラクタの強制呼び出しの対処が必要である
以上について理解を深めることができました。