JavaScript 再入門(その19) クラス class

クラス class

はじめに

クラス構文 class *ES2015 は、オブジェクトの記事で説明したとおり、クラスベースに近いコードが許されるようにした、プロトタイプベース言語のシンタックスシュガー(糖衣構文:ある構文を簡略化したり可読性をよくするための記法)です。このあたりの説明は過去の記事を参照してください。
JavaScriptにクラス構文がサポートされるようになり、プロトタイプベースの知識がなくともオブジェクト指向のコードが簡単に書けるようになりました。この記事ではクラス構文についてのみ説明します。

JavaScript 再入門(その14) オブジェクト 1 概要とデータ構造
概要とデータ構造 JavaScript は、オブジェクトベース言語 JavaScript のオブジェクトを説明するのに非常に悩ましい部分があります。 PHP の…
sakura-system.com
JavaScript 再入門(その15) オブジェクト 2 継承とプロトタイプチェーン
継承とプロトタイプチェーン はじめに これからオブジェクトのプロトタイプについての説明が続きますが、前の記事でも書いたように、ES2015 以降 class キ…
sakura-system.com
JavaScript 再入門(その16) オブジェクト 3 コンストラクタ関数
コンストラクタ関数 はじめに 前の記事に続き、オブジェクトのプロトタイプについての説明が続きます。前回は継承とプロトタイプチェーンの動作が理解しやすいように、オ…
sakura-system.com
JavaScript 再入門(その17) オブジェクト 4 オブジェクト間の継承
オブジェクト間の継承 はじめに 前の記事に続き、オブジェクトのプロトタイプについての説明が続きます。前回はコンストラクタ関数を使ってのプロトタイプチェーンの利用…
sakura-system.com

クラスの定義

クラス構文にはクラス宣言とクラス式の 2 つの定義方法があります。

クラス宣言

class Person {
	constructor( age , fname , lname ) {
		this.age = age ;
		this.fname = fname ;
		this.lname = lname ;
	}
	profile() {
		return( 'name:' +  this.lname + ' ' + this.fname + ' age:' +  this.age + ' ') ;
	} ;
}
// kato と yamashita のオブジェクト生成
let kato = new person( 20 , 'Ippei', 'Kato' ) ;
let yamashita = new person( 25 , 'Daisuke', 'Yamashita' ) ;
// それぞれの profile メソッドを実行
console.log( kato.profile() ) ; // name:Kato Ippei age:20
console.log( yamashita.profile() ) ; // name:Yamashita Daisuke age:25

クラス式

クラス式ではクラス宣言自体をデータとして保管することができます。

名前無しクラス式

let Person = class {
	constructor( age , fname , lname ) {
		this.age = age ;
		this.fname = fname ;
		this.lname = lname ;
	}
	profile() {
		return( 'name:' +  this.lname + ' ' + this.fname + ' age:' +  this.age + ' ') ;
	} ;
}

名前ありクラス式

let Person = class PersonClass {
	constructor( age , fname , lname ) {
		this.age = age ;
		this.fname = fname ;
		this.lname = lname ;
	}
	profile() {
		return( 'name:' +  this.lname + ' ' + this.fname + ' age:' +  this.age + ' ') ;
	} ;
}

クラス式ではクラスの再宣言が可能です。

// クラス式でクラスの宣言
let Person = class {
	constructor( age , fname , lname ) {
		this.age = age ;
		this.fname = fname ;
		this.lname = lname ;
	}
	profile() {
		return( 'name:' +  this.lname + ' ' + this.fname + ' age:' +  this.age + ' ') ;
	} ;
}
// オブジェクト生成
let kato = new Person( 20 , 'Ippei', 'Kato' ) ;
console.log( kato.profile() ) ; // name:Kato Ippei age:20
// クラスの再宣言
Person = class {
	constructor( model ) {
		this.model = model ;
	}
	profile() {
		return( 'car: ' + this.model ) ;
	}
}
// オブジェクト生成
kato = new Person('NISSAN GT-R') ;
console.log( kato.profile() ) ; // car: NISSAN GT-R

静的メソッド static

静的メソッドは static キーワードを使って定義します。
静的メソッドはオブジェクトを生成せずに呼び出せるメソッドです。
静的メソッドは動的メソッドと同じクラス内に存在しても構いませんが、静的メソッドはオブジェクトに属しません。

class Car {
	static nissan( model , profile ) {
		return ( profile + ' car: NISSAN ' + model ) ;
	}
	static mazda( model , profile ) {
		return ( profile + ' car: MAZDA ' + model ) ;
	}
}
// new でオブジェクト生成することなく呼び出すことができます。
console.log( Car.nissan( 'BNR32 GT-R V-specII' , 'Nakazato Takeshi' ) ) ;
// Nakazato Takeshi car: NISSAN BNR32 GT-R V-specII
console.log( Car.mazda( 'FD3S RX-7 Type R' , 'Takahashi Keisuke' ) ) ;
// Takahashi Keisuke car: MAZDA FD3S RX-7 Type R

同一のクラスに動的・静的メソッドが存在した例です。
静的メソッドの呼び出しは、オブジェクトではなくクラス名で呼び出します。

// クラスの宣言
class Person {
	constructor( age , fname , lname ) {
		this.age = age ;
		this.fname = fname ;
		this.lname = lname ;
	}
	// 動的メソッド
	profile() {
		return( 'name:' +  this.lname + ' ' + this.fname + ' age:' +  this.age ) ;
	} ;
	// 静的メソッド
	static nissan( model , profile ) {
		return ( profile + ' car: NISSAN ' + model ) ;
	}
	static mazda( model , profile ) {
		return ( profile + ' car: MAZDA ' + model ) ;
	}
	static toyota( model , profile ) {
		return ( profile + ' car: TOYOTA ' + model ) ;
	}
}
// オブジェクト生成
let fujiwara = new Person( 18 , 'Takumi' , 'Fujiwara' ) ;
let nakazato = new Person( 19 , 'Takeshi' , 'Nakazato' ) ;
let takahashi = new Person( 20 , 'Keisuke' , 'Takahashi' ) ;
// 静的メソッド呼び出しはオブジェクト名ではなくクラス名 Person で呼び出す。
console.log( Person.toyota( 'AE86 SPRINTER TRUENO GT-APEX 3door' , fujiwara.profile() ) ) ;
// name:Fujiwara Takumi age:18 car: TOYOTA AE86 SPRINTER TRUENO GT-APEX 3door
console.log( Person.nissan( 'BNR32 GT-R V-specII' , nakazato.profile() ) ) ;
// name:Nakazato Takeshi age:19 car: NISSAN BNR32 GT-R V-specII
console.log( Person.mazda( 'FD3S RX-7 Type R' , takahashi.profile() ) ) ;
// name:Takahashi Keisuke age:20 car: MAZDA FD3S RX-7 Type R

パブリックフィールド

コンストラクタ以外にクラス内で利用するフィールドを定義することができます。
デフォルト値の有無に関わらず宣言できますので、フィールドは常に存在するようになります。
デフォルト値がないフィールドはフィールドで宣言しても undefined となります。

// クラスの宣言
class Person {
	//** パブリックフィールド
	model = 'empty' ;
	maker ; // デフォルト値がないフィールドは undefined となります。
	static CAR = ' car: ' ; // パブリック静的フィールド
	// コンストラクター
	constructor( age , fname , lname ) {
		this.age = age ;
		this.fname = fname ;
		this.lname = lname ;
	}
	// メソッド
	profile() {
		let prof = `name: ${this.lname} ${this.fname} age: ${this.age}` ; // プロフィールを結合
		return( prof +		// 結合したプロフィール
				Person.CAR +	// パブリック静的フィールドはクラス名で呼び出す
				this.maker + ' ' + this.model	// パブリックフィールドから取得
			) ;
	} ;
}
// オブジェクト生成
let fujiwara = new Person( 18 , 'Takumi' , 'Fujiwara' ) ;
console.log(fujiwara.profile()) ;
// name: Fujiwara Takumi age: 18 car: undefined empty
// パブリックフィールドに値を設定
fujiwara.model = 'AE86 SPRINTER TRUENO GT-APEX 3door' ;
fujiwara.maker = 'TOYOTA' ;
console.log( fujiwara.profile() ) ;
// name: Fujiwara Takumi age: 18 car: TOYOTA AE86 SPRINTER TRUENO GT-APEX 3door

ゲッターとセッター

ゲッターとセッターによりプロパティのようにメソッドを呼び出すことができます。値を設定したり取得するときにメソッドが呼ばれるので、設定値の適正化やログ記録などの処理を実行することができます。

// クラスの宣言
class Person {
	model = 'empty' ;
	maker ;
	static CAR = 'car: ' ;
	constructor( age , fname , lname ) {
		this.age = age ;
		this.fname = fname ;
		this.lname = lname ;
	}
	//*** セッター
	set carmodel( argModel ) { // 車の型式を設定する
		this.model = argModel.toUpperCase() ; // 型式を大文字で設定する。
	}
	set carmaker( argMaker ) { // 車のメーカーを設定する
		this.maker = argMaker.toUpperCase() ; // メーカーを大文字で設定する。
	}
	//*** ゲッター
	get carProfile() { // 車のプロフィールを取得
		return `${Person.CAR}<${this.maker}> ${this.model}` ; // 文字列を編集
	}
	get myProfile() { // 自身のプローフィールを取得
		return `name: ${this.lname} ${this.fname} age: ${this.age}` ; // 文字列を編集
	}
	profile() { // メソッド内からもゲッターセッターは呼び出せる
		return this.myProfile + ' ' + this.carProfile ; // オブジェクトのプロパティから値を参照するように編集された文字列を取得
	} ;
}
// オブジェクト生成
let fujiwara = new Person( 18 , 'Takumi' , 'Fujiwara' ) ;
console.log(fujiwara.profile()) ; // name: Fujiwara Takumi age: 18 car: undefined empty
// セッターを使ってパブリックフィールドに値を設定
fujiwara.carmodel = 'AE86 SPRINTER TRUENO GT-APEX 3door' ;
fujiwara.carmaker = 'Toyota' ;
// ゲッターを使って取得
console.log( fujiwara.carProfile ) ;
// car: <TOYOTA> AE86 SPRINTER TRUENO GT-APEX 3DOOR <--- オブジェクトのプロパティから値を参照するように編集された文字列を取得
console.log( fujiwara.myProfile ) ;
// name: Fujiwara Takumi age: 18 <--- オブジェクトのプロパティから値を参照するように編集された文字列を取得
console.log( fujiwara.profile() ) ;
// name: Fujiwara Takumi age: 18 car: <TOYOTA> AE86 SPRINTER TRUENO GT-APEX 3DOOR

上記の例では、セッターを使うことにより、メソッドにて toUpperCase() で必ず大文字で設定されるようになります。

プライベートフィールド

ハッシュ # 接頭辞を使うことによりプライベートフィールドを生成することができます。
この # は名前の一部で、宣言やアクセスにも使用されます。
プライベートフィールドは呼び出される前に宣言しなくてはなりません。
プライベートフィールドはスコープ外から参照すると構文エラーとなります。

class Person {
	// プライベートフィールドの宣言
	#age ;	
	#fname ;
	constructor( age , fname , lname ) {
		this.#age = age ;
		this.#fname = fname ;
		this.#lname = lname ; // 宣言されていないので構文エラーとなる Uncaught SyntaxError: Private field '#lname' must be declared in an enclosing class
	}
}
class Person {
	// プライベートフィールドの宣言
	#age ;	
	#fname ;
	#lname ; 
	constructor( age , fname , lname ) {
		this.#age = age ;
		this.#fname = fname ;
		this.#lname = lname ;
	}
}
let fujiwara = new Person( 18 , 'Takumi' , 'Fujiwara' ) ;
console.log( fujiwara.#fname ) ; // スコープ外なので構文エラーとなる Uncaught SyntaxError: Private field '#fname' must be declared in an enclosing class

プライベートフィールドは static なフィールドにもメソッドにも使用できます。

プライベートフィールドへの値の設定や参照は、ゲッターとセッターを使って行います。

// クラスの宣言
class Person {
	// プライベートフィールドの宣言
	#model = 'empty' ;
	#maker ;
	#age ;	
	#fname ;
	#lname ;
	static #CAR = 'car: ' ;
	constructor( age , fname , lname ) {
		this.#age = age ;
		this.#fname = fname ;
		this.#lname = lname ;
	}
	set carmodel( argModel ) {
		this.#model = argModel.toUpperCase() ; // プライベートフィールドへ値を設定する。
	}
	set carmaker( argMaker ) {
		this.#maker = argMaker.toUpperCase() ; // プライベートフィールドへ値を設定する。
	}
	get carProfile() {
		const maker = Person.#editMaker( this.#maker ) ; // プライベートメソッドを呼び出す
		return `${Person.#CAR}${maker} ${this.#model}` ; // プライベートフィールドの値を参照する。
	}
	get myProfile() {
		return `name: ${this.#lname} ${this.#fname} age: ${this.#age}` ; // プライベートフィールドの値を参照する。
	}
	static #editMaker( argMaker ) { // プライベートメソッド、スコープ外からは利用できない
		return '<' +  argMaker + '>' ;
	}
	profile() {
		return this.myProfile + ' ' + this.carProfile ;
	} ;
}
// オブジェクト生成
let fujiwara = new Person( 18 , 'Takumi' , 'Fujiwara' ) ;
console.log(fujiwara.profile()) ;
// name: Fujiwara Takumi age: 18 car: undefined empty
fujiwara.carmodel = 'AE86 SPRINTER TRUENO GT-APEX 3door' ;
fujiwara.carmaker = 'Toyota' ;
console.log( fujiwara.carProfile ) ;
// car: <TOYOTA> AE86 SPRINTER TRUENO GT-APEX 3DOOR
console.log( fujiwara.myProfile ) ;
// name: Fujiwara Takumi age: 18
console.log( fujiwara.profile() ) ;
// name: Fujiwara Takumi age: 18 car: <TOYOTA> AE86 SPRINTER TRUENO GT-APEX 3DOOR

プライベートフィールドへのスコープ外からの参照は構文エラーとなります。

fujiwara.#model = 'AE86 SPRINTER TRUENO GT-APEX 3door' ; // Uncaught SyntaxError
console.log( Person.#editMaker('TOYOTA') ) ; // Uncaught SyntaxError

extends によるクラスの拡張

extends キーワドはクラスを別クラスの子クラス(サブクラス)として継承してクラスの機能を拡張します。
super キーワードは親クラス(スーパークラス)のメソッドを呼び出せます。メソッドを指定しない super() は、親クラスのコンストラクタを呼び出します。

// 親クラス Person の宣言
class Person {
	constructor( age , fname , lname ) {
		this.age = age ;
		this.fname = fname ;
		this.lname = lname ;
	}
	get myProfile() {
		return `name: ${this.lname} ${this.fname} age: ${this.age}` ;
	}
}
// 子クラス MyCar の宣言
class MyCar extends Person {
	#model = 'empty' ;
	#maker ;
	static #CAR = 'car: ' ;
	constructor( age , fname , lname , maker , model ) {
		super( age , fname , lname ) ; // 親クラス Person のコンストラクタを呼び出す
		this.#maker = maker.toUpperCase() ;
		this.#model = model.toUpperCase() ;
	}
	get carProfile() {
		return `${MyCar.#CAR}<${this.#maker}> ${this.#model}` ;
	}
	profile() {
		return this.myProfile + ' ' + this.carProfile ;
	} ;
}
// オブジェクト生成
let fujiwara = new MyCar( 18 , 'Takumi' , 'Fujiwara' , 'Toyota' , 'AE86 SPRINTER TRUENO GT-APEX 3door' ) ;
console.log( fujiwara.carProfile ) ; // car: <TOYOTA> AE86 SPRINTER TRUENO GT-APEX 3DOOR
console.log( fujiwara.myProfile ) ; // name: Fujiwara Takumi age: 18
console.log( fujiwara.profile() ) ; // name: Fujiwara Takumi age: 18 car: <TOYOTA> AE86 SPRINTER TRUENO GT-APEX 3DOOR

super.メソッド の働き

class Parent {
	method() { return 'Parent' ; }
}
class Child extends Pearent {
	method() {return 'Child' ; } // 親クラス Pearent と同じ名前のメソッド
	test() {
		console.log( 'this:' , this.method() ) ; // this: Child
		console.log( 'super:' , super.method() ) ; // super: Parent ****
	}
}
let obj = new Child ;
obj.test() ;
// this: Child
// super: Parent

子クラスから親クラスのプライベートフィールドへ参照も、スコープ外のために構文エラーとなるので、ゲッターなどを使って参照する。

class Parent {
	#dad = 'Daddy' ;
	#mom = 'Mommy' ;
	get dad() { return this.#dad ; }
	get mom() { return this.#mom ; }
}
class Child extends Parent {
	parent() {
		console.log( this.dad ) ;
		console.log( this.mom ) ;
//		console.log( this.#dad ) ; // Uncaught SyntaxError
//		console.log( this.#mom ) ; // Uncaught SyntaxError
	}
}
let babe = new Child ;
babe.parent() ;
// Daddy
// Mommy

参考リンク

MDN 開発者向けのウェブ技術 > クラス
MDN 開発者向けのウェブ技術 > 文と宣言 > class
MDN 開発者向けのウェブ技術 > 式と演算子 > クラス式
MDN 開発者向けのウェブ技術 > クラス > static
MDN 開発者向けのウェブ技術 > クラス > パブリッククラスフィールド
MDN 開発者向けのウェブ技術 > ゲッター
MDN 開発者向けのウェブ技術 > セッター
MDN 開発者向けのウェブ技術 > クラス > プライベートクラス機能
MDN 開発者向けのウェブ技術 > クラス > extends
MDN 開発者向けのウェブ技術 > 式と演算子 > super
MDN 開発者向けのウェブ技術 > JavaScript「再」入門