ECMAScript 6でDateクラスを継承してみる ー クラス定義の基本

ECMAScript 2015 (ECMAScript 6)では、ビルトインクラスが継承できるようになりました。そこで、Dateのサブクラスをお題に、新しいクラスの基本的な構文を確かめていきましょう。

#クラスの宣言と継承
クラスは[`class`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/class)キーワードで名前(識別子)をつけて宣言します。継承するクラスがあるときは、さらに[`extends`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Classes/extends)キーワードを加えて定めます(「[クラス](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Classes)」参照)。

```js
class DateEx extends Date {
}
```

#コンストラクタの呼び出し
インスタンスをつくるコンストラクタは、専用のメソッド[`constructor()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Classes/constructor)で定めます。サブクラスの場合は、スーパークラスのコンストラクタをメソッド本体から呼び出すことになります。そのときに用いるのが[`super`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/super)キーワードです。

`Date`クラスのコンストラクタは、渡される引数の数が決まっていません。このようなときは[残余引数`...`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions_and_function_scope/rest_parameters)を使います。数の定まらない引数がまとめて配列として受け取れるのです。さらに、メソッドを呼び出すとき、同じかたちの[スプレッド構文`...`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/Spread_syntax)で、配列をカンマ区切りの引数に直して渡せます。

```js
class DateEx extends Date {
	constructor(...args) {
 		super(...args);
	}
}
```

クラスのコンストラクタには、実は[デフォルトの定め](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Classes/constructor#Default_constructors)があります。そして、サブクラスのデフォルトがまさにうえの`constructor()`の構文どおりなのです。したがって、この場合コンストラクタは省いて構いません。

```js
class DateEx extends Date {
	/* constructor(...args) {
 		super(...args);
	} */
}
const date = new DateEx(2018, 0, 1);
console.log(date);  // Mon Jan 01 2018 00:00:00 GMT+0900 (JST)
console.log(date instanceof DateEx);  // true
```

#クラスにインスタンスメソッドを加える
クラスのインスタンスメソッドは、メソッド名を与えて定めます。[`Date.prototype. getDay()`メソッド](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date/getDay)は日曜日を0とする整数連番を返しますので、配列に納めた対応する漢字の曜日を返すこととしました。

```js
class DateEx extends Date {
	getDayJp(full) {
		const daysJp = ['日', '月', '火', '水', '木', '金', '土'];
		return daysJp[this.getDay()] + (full ? '曜日' : '');
	}
}
const date = new DateEx(2018, 0, 1);
console.log(date.getDayJp(true));  // 月曜日
```

関数やメソッドの引数には、代入演算子`=`で[デフォルト値](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Functions_and_function_scope/Default_parameters)が与えられます。引数が渡されないときは、その値が用いられるのです。

```js
class DateEx extends Date {
	getDayJp(full = false) {
		const daysJp = ['日', '月', '火', '水', '木', '金', '土'];
		return daysJp[this.getDay()] + (full ? '曜日' : '');
	}
}
const date = new DateEx(2018, 0, 1);
console.log(date.getDayJp());  // 月
```

#getter/setterメソッドを定める
getter/setterメソッドを定めると、プロパティのように値を取り出したり書き替えたりできます。構文はそれぞれ、`get`あるいは`set`をメソッドの頭に添えます。getterは引数なしに値を返し、setterは受け取る引数がひとつだけで戻り値はありません。

```js
class DateEx extends Date {
	get fullYear() {
		return this.getFullYear();
	}
	set fullYear(year) {
		this.setFullYear(year);
	}

}
const date = new DateEx(2018, 0, 1);
console.log(date.fullYear);  // 2018
date.fullYear = 2020;
console.log(date);  // Wed Jan 01 2020 00:00:00 GMT+0900 (JST)
```

同じように、月日もgetter/setterメソッドにしましょう。前に加えたインスタンスメソッドも、getterから値を得るように書き替えます。

```js
class DateEx extends Date {

	get month() {
		return this.getMonth();
	}
	get date() {
		return this.getDate();
	}
	get day() {
		return this.getDay();
	}

	set month(month) {
		this.setMonth(month);
	}
	set date(date) {
		this.setDate(date);
	}
	getDayJp(full = false) {
		const daysJp = ['日', '月', '火', '水', '木', '金', '土'];
		// return daysJp[this.getDay()] + (full ? '曜日' : '');
		return daysJp[this.day] + (full ? '曜日' : '');
	}
}
```

#クラスに静的なメソッドを加える
クラスを参照して呼び出す静的なメソッドも定められます。メソッドの頭に添えるキーワードは[`static`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Classes/static)です。`Date`インスタンスの日付を、日本語の文字列で示してみます。

```js
class DateEx extends Date {

	static toJpDate(date) {
		const jpDate =
		date.fullYear + '年' + (date.month + 1) + '月' + date.date + '日'
		+ date.getDayJp(true);
		return jpDate;
	}
}
const date = new DateEx(2018, 0, 1);
console.log(DateEx.toJpDate(date));  // 2018年1月1日月曜日
```

#クラス定義ははじめに書く
クラスを定めるとき注意しなければならないのは、はじめに書くということです。`function`と異なり、あとに書いた定義が先読み(「[ホイスティング](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Classes#%E3%82%AF%E3%83%A9%E3%82%B9%E5%AE%A3%E8%A8%80)」と呼ばれます)されません。呼び出す前に、クラスを宣言しておかなければならないのです。

ECMAScript 6におけるクラスの定め方は、MDN「[クラス](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Classes)」に詳しく解説されています。本稿は、具体的なコード例でその基本を簡単にまとめました。これまで定めた`Date`のサブクラスは、以下にまとめておきましょう。

```js
class DateEx extends Date {
	get fullYear() {
		return this.getFullYear();
	}
	get month() {
		return this.getMonth();
	}
	get date() {
		return this.getDate();
	}
	get day() {
		return this.getDay();
	}
	set fullYear(year) {
		this.setFullYear(year);
	}
	set month(month) {
		this.setMonth(month);
	}
	set date(date) {
		this.setDate(date);
	}
	getDayJp(full = false) {
		const daysJp = ['日', '月', '火', '水', '木', '金', '土'];
		return daysJp[this.day] + (full ? '曜日' : '');
	}
	static toJpDate(date) {
		const jpDate =
		date.fullYear + '年' + (date.month + 1) + '月' + date.date + '日'
		+ date.getDayJp(true);
		return jpDate;
	}
}
```