【JavaScript】クラス定義と使い方とその他いろいろ
更新日:2024/02/27
JavaScriptにもクラスがあった。
今更知った僕です。
そこでさっそく、JavaScriptのクラスについて調べてみました。
- 1クラスの定義と使い方
- class定義
- インスタンスの生成
- インスタンスの初期化
- パブリックフィールド定義
- パブリックメソッド定義
- パブリックゲッター/セッター
- プライベートフィールド定義
- プライベートメソッド定義
- プライベートゲッター/セッター
- 静的メンバー
- 静的ブロック
- 補足:定数の定義
- 2クラスで継承をおこなう
- 継承の宣言
- 親クラスのコンストラクタ呼び出し(必須)
- メソッドのオーバーライドと親メソッドの呼び出し
- 3JavaScriptのclassでカプセル化
- 4クラスはstrictモードで動作する
- 5JavaScriptのクラスと一般的なクラスとの違い
- 一般的なクラスとインスタンス
- JavaScriptのインスタンス
- JavaScriptのクラス
- ECMAScript2015のclass構文
- ECMAScript2022のclass構文
- 6まとめ
2023/10 ソースコードの内容が意図しないものになっていたため訂正しました
クラスの定義と使い方
まずはJavaScriptのクラス定義と使い方について見ていきます。
class定義
class定義で使用できる機能を、コードで一覧してみます。
class a extends b{
// ********** インスタンス初期化メソッド
constructor( v ) { // constructorメソッド
super(); // extendsした場合は必ず記述
this.member = v;
this.#privateValue = v;
}
// ********** パブリックメンバーに関する定義
value = 1; // パブリックフィールド
method = function(){}; // パブリックフィールド
prototypeChainMethod(){ console.log(this.#privateValue) };// パブリックメソッド
get setGet(){ return this.value }; // パブリックゲッター
set setGet( v ){ this.value = v; }; // パブリックセッター
get CONSTANT_VALUE (){ return "定数値"; }; // インスタンス定数
// ********** プライベートメンバーに関する定義
#privateValue = 2; // プライベートフィールド
#privateMethod(){}; // プライベートメソッド
get #privateSetGet(){ return this.#privateValue }; // プライベートゲッター
set #privateSetGet( v ){ this.#privateValue = v; }; // プライベートセッター
// ********** 静的メンバーに関する定義
static staticField; // 静的変数
static staticMethod(){ // 静的メソッド
this.#staticPrivetMethod();
};
static get staticGetSet(){ return this.staticField; }
static set staticGetSet( v ){ this.staticField = v; }
static #staticPrivetFIled; // 静的プライベート変数
static #staticPrivetMethod(){ // 静的プライベートメソッド
console.log( this.staticGetSet * this.#staticPrivetGetSet );
};
static get #staticPrivetGetSet(){ return this.#staticPrivetFIled; }
static set #staticPrivetGetSet( v ){ this.#staticPrivetFIled = v; }
static { // 静的ブロック
this.staticGetSet = 5;
this.#staticPrivetGetSet = 100;
}
static get CONSTANT_VALUE (){ return "定数値"; }; // クラス定数
}
インスタンスの生成
次のコードは、一番簡単なクラス定義と、定義からインスタンスを作成する方法です。
class宣言
class a{ // クラスaを宣言
}
const b = new a(); // クラスaのインスタンスを作成
次のようにクラス式として、変数に代入できます。
class式
const a = class{ // クラスを宣言し、変数aに代入
};
const b = new a(); // 変数aから、インスタンスを作成
また、constructorメソッドに引数を渡さないときは( )を省略できます。
const b = new a();
const c = new a; // ( )を省略できる
インスタンスの初期化
constructorメソッドは、classをnew演算子でインスタンス化するときに呼び出されます。
次のように、クラス内部でconstructorメソッドを定義します。
class a{
constructor( ) { // constructorメソッド
this.value = 1;
this.func = function(){ };
}
}
const b = new a();
console.log( b ); // Object { value: 1 }
constructorメソッド内ではthis値にプロパティを定義していきます。
最後にthis値がリターンされ、インスタンスとして扱われます。
これはコンストラクター関数と同じですね。
ただし、後述のパブリックフィールドやプライベートフィールドなどが定義されている場合、this値にはあらかじめそれらフィールドがプロパティとしてセットされています。
class a{
value = 1; // パブリックフィールド
#value2 = 2; // プライベートフィールド
constructor( ) { // constructorメソッド
console.log( this ); // Object { value: 1, #value2: 2 }
this.value = 10;
this.#value2 = 100;
}
}
const b = new a();
console.log( b ); // Object { value: 10 , #value2: 100 }
プライベートフィールドが定義されていない場合、constructorメソッド内でプライベートプロパティの追加や値変更することはできません。
class a{
// value = 1; ←定義を削除
// #value2 = 2; ←定義を削除
constructor( ) { // constructorメソッド
console.log( this ); // Object { }
this.value = 10; // これは可能
this.#value2 = 100; // Uncaught SyntaxError: reference to undeclared private field or method #value2
}
}
const b = new a();
また、constructorは引数を受け取ることができます。
constructorに引数を渡す
class a{
constructor( v ) { // constructorメソッド
this.value = v;
}
}
const b = new a(100);
console.log( b ); // Object { value: 100 }
パブリックフィールド定義
パブリックメンバーは、インスタンスのプロパティとして生成されます。
生成されたプロパティは、値の変更や削除など通常のプロパティと同じように扱うことができます。
class a{
value = 1; // パブリックフィールド
}
const b = new a();
console.log( b ); // Object { value: 1 }
b.value = 100;
delete b.value;
なおプリミティブ値ではなくて関数を代入した場合でも、パブリックフィールドです。
パブリックメソッドではありません。
class a{
value = function(){ }; // パブリックフィールド
}
const b = new a();
console.log( b ); // Object { value: function(){ } }
プリミティブ値も関数もJavaScriptではデータとして扱われます。
上記はあくまで、プロパティにデータをセットしただけなのです。
パブリックフィールドは、ECMAScript2022で導入された機能です。
パブリックメソッド定義
パブリックメソッドは、インスタンスのプロトタイプチェーンに組み込まれるメソッドです。
class a{
value = 1;
getValue(){
return this.value1;
}
}
const b = new a();
console.log( b ); // Object { value: 1 }
console.log( Object.getPrototypeOf( b ) ); // Object { getValue: function(){ } , ・・・ }
パブリックメソッドは全てのインスタンスが共有します。
そのため、パブリックメソッドの内容を変更すると他のインスタンスも影響を受けます。
class aa{
func(){
return 1;
}
}
const obj1 = new aa;
const obj2 = new aa;
console.log( obj1.func() , obj2.func() ); // 1 , 1
Object.getPrototypeOf( obj1 ).func = function (){ return 100; } // obj1のfuncメソッドを変更
console.log( obj1.func() , obj2.func() ); // 100 , 100 ← obj2も影響を受けた
パブリックゲッター/セッター
パブリックゲッター/セッターはパブリックメソッド と同じように、インスタンスのプロトタイプチェーンに組み込まれるゲッター/セッターです。 プライベートフィールドは先頭に"#"を付加することで、自動的に生成されます。 プライベートフィールドはclass定義内で定義されているメソッドおよびゲッター/セッターからthisキーワードを経由してのみアクセスできます。 プライベートフィールドは、ECMAScript2022で導入された機能です。 プライベートメソッドはプライベートフィールドと同じように、メソッドおよびゲッター/セッターからthisキーワードを経由してのみアクセスできます。 プライベートメソッドは、ECMAScript2022で導入された機能です。 プライベートゲッター/セッターはプライベートフィールドと同じように、メソッドおよびゲッター/セッターからthisキーワードを経由してのみアクセスできます。 プライベートゲッター/セッターは、ECMAScript2022で導入された機能です。 静的メンバーは、classが所持するプロパティです。 プライベートなプロパティは、静的メソッドからthis経由でアクセスします。 次のコードは、パブリックと静的メンバーの両方に同名のフィールドとメソッドを生成しています。 二つのmethodメソッド内のthisは、それぞれ異なるものを参照していることに注意する必要があります。 静的メソッドおよびゲッター/セッター以外は、ECMAScript2022で導入された機能です。 静的ブロックは、class定義がソースコードから読み込まれてclassが生成されるときに実行されます。 静的ブロックは、ECMAScript2022で導入された機能です。 classには定数を定義する構文がありません。 クラス定数は、クラス名.定数名 でアクセスする定数です。 静的ゲッターでプリミティブを返すことで、定数を定義します。 静的ブロックでObject.definePropertyを実行して、this値にプロパティを追加します。 Object.definePropertyのwritable属性の規定値はfalseなので、valueのみ指定すればOKです。 静的ゲッターが呼び出した関数内でプリミティブを返すのに対して、こちらは直接プリミティブを返しています。 インスタンス定数は、インスタンスが所持する定数です。 パブリックゲッターでプリミティブを返すことで、定数を定義します。 静的ブロックでObject.definePropertyを実行して、this値のprototypeプロパティにプロパティを追加します。 Object.definePropertyのwritable属性の規定値はfalseなので、valueのみ指定すればOKです。 他の言語でプログラムをしてきた人が、クラスに期待することの一つに継承があると思います。 JavaScriptの継承は他の言語と少し意味合いが異なります。 ここでは、class構文の継承機能についてお伝えします。 class構文はextendsキーワードを使用することで、継承を行うことができます。 親クラスはクラス宣言したものだけでなく、コンストラクターを指定できます。 ArrayやStringなどの組み込みオブジェクトも、コンストラクターなので継承できます。 extendsキーワードを使用したら、必ず子クラスのconstructorで最初にsuper()を使用します。 super()は、親クラスのconstructorを呼び出します。 上の例のように、親クラスのconstructorは子クラスのthisを操作します。 親クラスと子クラスで同名のメソッドを持っている場合、インスタンスからの呼び出しは子クラスのメソッドが実行されます。 親クラスからthisでメソッドを実行すると、子クラスのメソッドが呼び出されます。 上の例は親と子それぞれ、bbb()というメソッドを持っています。 その結果親クラスも子クラスも、子のbbb()を呼び出します。 親クラスのbbb()が実行されないという点がポイントですね。 他のプログラム言語のクラスでできることの一つが、情報を隠蔽してカプセル化できるということですね。 class構文導入時はprivateもprotectedもなかったので、JavaScriptのプロパティはオープンでpublicなままでした。 従来のclass構文での情報隠蔽はクロージャ的な手法で実現していました。 プライベートフィールドが導入される以前に書いたので、あまり意味がなくなってしまった記事(涙 ECMAScript2022以降は、プライベートフィールドだけでなくパブリックフィールドなどを使用することで簡単にカプセル化とカプセル化したデータへのアクセスができます。 class構文内のコードは強制的にstrictモードになります。 strictモードは変数の定義をしなくてもそれなりに動いてくれるなど少し適当だったJavaScriptを、ほんの少し厳格にするモードです。 デフォルトでは非strictモードで動作するので、strictモードを使用するときは"use strict";と記述する必要があります。 しかしクラスは、記述してもしなくてもstrictモードとなります。 非strictモードでプログラムを組んでいる人は、クラス内だけエラーとなり戸惑うかもしれませんね。 ここからはJavaScriptのクラスと一般的なクラスとの違いについて、解説してみます。 一般的にはクラスとは、オブジェクトが所持する変数やメソッドなどを定義したものです。 クラスという設計図から、オブジェクトという実体を作成することで、初めて変数やメソッドを実行できるようになります。 クラスから生成されたオブジェクトを○○(クラス名)のインスタンスと呼びます。 class構文が導入される以前から、JavaScriptにはインスタンスが存在していました。 JavaScriptのインスタンスはコンストラクター関数をnew演算子で実行して、その結果得られたオブジェクトを指します。 コンストラクター関数からインスタンスを生成 JavaScriptの関数はfunctionという名前のオブジェクトです。 生成されたインスタンスのプロトタイプチェーンには、コンストラクター関数のprototypeプロパティが組み込まれます。 JavaScriptはプロトタイプベースなプログラミング言語と位置付けられています。 ”オブジェクト指向にはクラスがあるのが当然”という考え方がるのか、コンストラクター関数をクラスとして紹介しているネット記事が以前は多くありました。 しかしどうしても”オブジェクト指向にはクラスがあるのが当然”という声が強いのか、ECMAScript2015でclass構文が追加されました。 その実態はコンストラクター関数(functionオブジェクト)を生成するものでした。 つまりクラスが導入されたのではなくて、クラスっぽい構文でコンストラクター関数を生成する機能が導入されたのです。 例えば次のコードのコンストラクター関数とクラス定義は、ほぼ同じ内容のfunctionオブジェクトを生成します。 typeof演算子は値の型を文字列で返します。 ECMAScript2015のclass構文は、メソッドのみ指定できました。 ECMAScript2015のclass構文 class構文導入以前からJavaScriptを使用してきたプログラマーからすると、あまり魅力を感じるものではありませんでした。 ECMAScript2022では、class定義内でプロパティを定義できるようになりました。 また従来のオブジェクト操作との差別化なのか、プライベートなプロパティとメソッドをclass定義のみで実現可能です。 導入されたプライベートプロパティについては、次のページを読んでみてください! ECMAScript2022でのclass定義の機能追加で、他言語のプログラマがイメージするクラスに近づいてきました。 今後はclass構文の使用頻度が増えていくのが予想できます。 class構文を使用すれば、プロトタイプチェーンなどの仕組みを理解しなくてもインスタンスが作成できます。 更新日:2024/02/27
class a{
value = 1;
get setGet(){ return this.value }; // パブリックゲッター
set setGet( v ){ this.value = v; }; // パブリックセッター
}
const b = new a();
console.log( b ); // Object { value: 1 }
console.log( Object.getPrototypeOf( b ) ); // Object { <get setGet()>: function setGet() , <set setGet()>: function setGet(v) }
プライベートフィールド定義
プライベートフィールドはインスタンス内に生成されるプロパティですが、外部からアクセスできません。
class aa{
#privateValue = 2;
}
const b = new aa();
console.log( b.#privateValue); // Uncaught SyntaxError: reference to undeclared private field or method #privateValue
ただし、静的メソッドからはアクセスできません。
class aa{
#privateValue = 2;
method = function(){ return this.#privateValue};
publicMethod(){ return this.#privateMethod() };
#privateMethod(){ return this.#privateValue};
}
const b = new aa();
console.log( b.method() , b.publicMethod() ); // 2 , 2
プライベートメソッド定義
ただし、プライベートメソッドは値を上書きできません。
class aa{
#privateValue = 2;
#privateMethod(){ return this.#privateValue};
publicMethod(){ this.#privateMethod = function (){}};
}
const b = new aa();
b.publicMethod(); // Uncaught TypeError: cannot assign to private method
プライベートゲッター/セッター
静的メンバー
パブリックなプロパティは、class名で直接アクセスできます。
class aa{
static value = 2;
static func(){ return this.value * 2; };
}
console.log( aa.value , aa.func() ); // 2 , 4
class aa{
static #value = 2;
static func(){ return this.#value * 2; };
}
console.log( aa.func() ); // 4
class aa{
value = 2;
method(){ return this.value}; // インスタンスのthisを参照
static value = 20;
static method(){ return this.value; } // class aaのthisを参照
}
console.log( aa ); // class { value:20 , method:function(){} , ・・・}
console.log( aa.method() ); // 20
const b = new aa();
console.log( b ); // class { value:2 }
console.log( b.method() ); // 2
静的ブロック
静的ブロック内でのthisのメンバーは、静的フィールドやメソッドです。
class aa{
value = 2;
static value = 20; // ①
static {
this.value = 200; // このvalueは①
}
}
補足:定数の定義
ですが既存の構文やメソッドを使用することで、定義できます。クラス定数の定義
方法は2種類あります。静的ゲッターで定義
class a {
static get CONSTANT_VALUE (){ return "定数値"; };
}
console.log( a.CONSTANT_VALUE ); // "定数値"
Object.definePropertyで定義
このとき上書き属性をfalseにすることで、定数として機能します。
class a {
static {
Object.defineProperty( this , "CONSTANT_VALUE",{
value:"定数値",
/* writable:false */ // 規定値はfalse
})
}
}
console.log( a.CONSTANT_VALUE ); // "定数値"
処理速度はこちらの方が速いですが、アプリケーションの動作に影響を与えるほど遅くなることはほとんどありません。
静的ゲッターの方がわかりやすいので、そちらがおススメです。インスタンス定数の定義
こちらも、方法は2種類あります。パブリックゲッターで定義
class a {
get CONSTANT_VALUE (){ return "定数値"; };
}
const b = new a();
console.log( b.CONSTANT_VALUE ); // "定数値"
Object.definePropertyで定義
このとき上書き属性をfalseにすることで、定数として機能します。
class a {
static {
Object.defineProperty( this.prototype , "CONSTANT_VALUE",{
value:"定数値",
/* writable:false */ // 規定値はfalse
})
}
}
const b = new a();
console.log( b.CONSTANT_VALUE ); // "定数値"
ここで設定した定数プロパティは、インスタンスのプロトタイプチェーンに組み込まれます。クラスで継承をおこなう
詳しくは次のページを読んでみてください。
■【JavaScript】 JSにおける継承とは何を指しているのか継承の宣言
class b{
}
class a extends b{
}
function b( ){
}
b.prototype.x = function(){};
class a extends b{
}
class a extends String{
}
親クラスのコンストラクタ呼び出し(必須)
使用しないとエラーになります。
class b {
constructor( ) {
this.x = 5;
}
}
class a extends b{
constructor( ) {
// super( ) を呼び出していない
// ReferenceError:
// Must call super constructor in derived class
// before accessing 'this' or returning from derived constructor
}
}
const c = new a; // エラーになる
このとき親クラスのconstructor内のthisは、子クラスのthisです。
class b {
constructor( ) {
this.x = 5; // 子クラスaのthisを操作
}
}
class a extends b{
constructor( ) {
super( ) // this { x:5 }
this.x = 100; // this { x:100 }
// 親クラスでセットした x : 5 は失われる
}
}
const c = new a;
親クラス自身は、thisを保持していません。
子クラスで同名のプロパティに値をセットすると、親クラスでセットした値は失われます。メソッドのオーバーライドと親メソッドの呼び出し
子クラスのメソッドでsuperを使用すると親が持つメソッドを実行できます。
class b { // 親クラス
constructor( ) { }
aaa(){ // (2)
console.log("Super Class");
}
}
class a extends b { // 子クラス
constructor( ) { }
aaa(){ // (1)
super.aaa(); // (2)が呼ばれる
}
}
const c = new a;
c.aaa( ); // (1)が呼ばれる
class b { // 親クラス
constructor( ) { }
aaa(){ // (1)
this.bbb(); // 子クラスのbbb()が呼ばれる
}
bbb(){
console.log("Super Class"); // 呼び出されない!!
}
}
class a extends b { // 子クラス
constructor( ) { }
aaa(){ // (2)
this.bbb(); // 子クラスのbbb()が呼ばれる
super.aaa();
}
bbb(){
console.log("Sub Class");
}
}
const c = new a;
c.aaa( );
そして(1)と(2)で、親子ともにbbb()というメソッドを呼び出しています。JavaScriptのclassでカプセル化
しかしECMAScript2022でプライベートフィールドとメソッドが導入されたことで、ようやく簡単にカプセル化できる環境が整いました。
class a {
constructor( v1 , v2 ) {
let _name = v2;
let _familyName = v1;
Object.defineProperties( this ,{
name:{
set:( name )=>_name=name,
get: () => _name
},
familyName:{
set:( familyName )=>_familyName=familyName,
get: () => _familyName
},
fullName:{
get: () => `${_familyName} ${_name}`
}
})
}
}
const b = new a( "山田 " , "たろう" );
console.log( b.fullName ); // 山田たろう
■【JavaScript】 class構文でのprivate変数定義のひな型パターン
class a {
#name; // プライベートフィールド
#familyName; // プライベートフィールド
constructor( v1 , v2 ) {
this.#name = v2;
this.#familyName = v1;
}
get name(){ return this.#name; }
set name( name ){ this.#name = name; }
get familyName(){ return this.#familyName; }
set familyName( familyName ){ this.#familyName = familyName; }
get fullName(){ return `${this.#familyName} ${this.#name}`; }
}
const b = new a( "山田 " , "たろう" );
console.log( b.fullName ); // 山田たろう
クラスはstrictモードで動作する
JavaScriptのクラスと一般的なクラスとの違い
一般的なクラスとインスタンス
クラスそのものは『定義しているだけ』です。
(静的メソッドは実体がなくても実行できるけれど、とりあえずおいておいてください…)JavaScriptのインスタンス
しかしクラスが存在していないので、一般的なインスタンスとは異なるものです。
// コンストラクター関数
const constructorFunction = function (){
this.value = 100; // インスタンスにvalueプロパティを追加
this.func = ()=>{}; // インスタンスにfuncメソッドを追加
}
// constructorFunctionからinstanceObjを生成
const instanceObj = new constructorFunction();
そのためJavaScriptのインスタンスは、オブジェクトから生成されたオブジェクトとも言えます。
そのため、インスタンスはコンストラクター関数を継承していると表現されることもあります。JavaScriptのクラス
プロトタイプベースは既存オブジェクトからクローンを作成して、そこにメンバを構成していきます。
その対義的なものにクラスベースがあります。
クラスだと言われて素直に納得できる人は、あまりいませんでした。
プロトタイプベースをクラスベースとして考えることに無理があるので当然なのです。
『JavaScriptにやっとクラスが導入された!』という喜びの声が上がりましたが、プロトタイプベースからクラスベースに変更とかしたら別の言語になってしまいます。さすがにそこまではやれません。
クラスからインスタンスを生成しているように見えても、実際にはコンストラクター関数がインスタンスを生成しているのです。
// コンストラクター関数
const constructorFunction = function (){
this.value = 100; // インスタンスにvalueプロパティを追加
this.func = ()=>{}; // インスタンスにfuncメソッドを追加
}
console.log( typeof constructorFunction ); // "function"
// クラス
const classFunction = class {
constructor( ) {
this.value = 100; // インスタンスにvalueプロパティを追加
this.func = ()=>{}; // インスタンスにfuncメソッドを追加
}
}
console.log( typeof classFunction ); // "function"
値がfunctionオブジェクトのときは、"function"が返ります。
クラスも"function"を返すので、functionオブジェクトだということがわかります。ECMAScript2015のclass構文
プロパティについてはconstructorメソッドの中で設定します。
const classFunction = class {
constructor( ) { // プロパティの初期化
this.value = 100; // インスタンスにvalueプロパティを追加
this.func = ()=>{}; // インスタンスにfuncメソッドを追加
}
method(){
// 処理
}
static staticMethod(){ // 静的メソッド
// 処理
}
}
ECMAScript2022のclass構文
またプライベートなプロパティとメソッドを定義できるようになりました。
これにより、他言語のクラス定義に近づいてきました。
そのためclass構文の使用を拒否していた層も、class構文を利用するケースが増えると予想されます。
■【JavaScript】 クラス定義でプライベートプロパティが使用できるようになったと聞いて確認してみたまとめ
また、プライベート要素をclass構文のみに導入することで、他のオブジェクト操作よりもclass構文を優位な位置に置くことができました。
『プロトタイプ何それ?』という声が増えそうです。
関連記事
スポンサーリンク
記事の内容について
こんにちはけーちゃんです。
説明するのって難しいですね。
「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。
裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。
掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。
ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php
このサイトは、リンクフリーです。大歓迎です。