クラスについて
JavaScriptもオブジェクト指向の概念を持つプログラミング言語ですので、
オブジェクト指向プログラミングを実現するために、クラスが存在しています。
ECMAScript6(ES6)から登場しました。
ES6以前のJavaScriptでは、プロトタイプベースと呼ばれる個々のオブジェクト同士を
派生させ、オブジェクト指向を実現することが主流でした。
ES6からは、クラスが登場し、他のプログラミング言語同様にクラスベースの
オブジェクト指向を実現することも出来るようになり、幅が広がりました。
また、ES2022からはクラスの記法も進化し、より柔軟性を持ったクラスを作成することができます。
コンストラクタ
ES6のクラス記法では、プロパティを設定する場合は、
コンストラクタを必ず記述する必要がありました。
ES2022からは、クラスのトップレベルでプロパティを設定することができます。
そのため、プロパティに値を設定するだけであれば、コンストラクタを省略することができます。
// ES6
class SampleES6 {
constructor() {
this.prop = 0
}
}
// ES2022
class SampleES2022 {
prop = 0
}
このように、ES6と比べると、かなり楽に書けるようになりました。
ただし、コンストラクタの引数を受け取って、プロパティに設定したい場合は、
コンストラクタ内で初期値を設定する必要があります。
これは状況によって、どちらを使うか判断すれば良いと思います。
class SampleES2022 {
constructor(arg) {
this.prop = 0
}
}
console.log(new SampleES2022().prop) // 0
アクセス権
次は、アクセス権について解説します。
他のプログラミング言語の勉強をしている方ならお馴染みのアクセス権ですが、
JavaScriptにアクセス権の概念は存在します。
ES2022以前は、全てパブリック(クラス外からも呼び出し可能)でしたが、
ES2022からは、プライベート(クラス内からのみ呼び出し可能)のアクセス権を、
使用できるようになりました。よって、2種類のアクセス権を使い分けることができます。
プライベートの定義
JavaScriptで、クラスのプロパティやメソッドのアクセス権をプライベートにして
定義したい場合は、プロパティやメソッドの前に「#」を付与します。
これを、プライベートプロパティ、プライベートメソッドと呼びます。
class Sample {
// public
item = 0
// private
#item2 = 1
// private
#print() {
console.log(`item:${this.item}\nitem2:${this.#item2}`)
}
// public
print() {
this.#print()
}
}
const sample = new Sample()
sample.print()
上記のサンプルコードでは、
プライベートプロパティとプライベートメソッドの定義をしています。
9行目を見ると、「this.#item2」と記述しています。
プライベートプロパティやプライベートメソッドを呼び出すときは先頭の#も一緒に記述します。
また、Sampleクラス外で「#print()」を呼び出そうとすると、エラーになる点にも注意です。
プライベートプロパティの注意点
パブリックなプロパティは、トップレベルでの定義が無くても、
コンストラクタやメソッド内で、使用することができましたが、
プライベートなプロパティを使用したい場合は、必ずトップレベルで宣言する必要があります。
class Sample {
// プライベートなプロパティはトップレベルで宣言する必要がある
// 下記の1行をコメントアウトするとエラーになる
#item
constructor() {
this.#item = 0
this.item2 = 1
console.log(this.#item)
console.log(this.item2)
}
}
const sample = new Sample()
// 0
// 1
プロトタイプ
冒頭でも少し出てきましたが、JavaScriptは、プロトタイプと呼ばれるオブジェクト同士を結合させ、
オブジェクト指向を実現するプロトタイプベースの言語になります。
ES6でクラスが登場してからも、実際に裏で動いているのは、プロトタイプになるので、
JavaScriptの知識を深めるためには、プロトタイプについても知る必要があります。
今回は、プロトタイプについて解説していきます。
プロトタイプの特徴
ここでは、JavaScriptのプロトタイプについて深堀りするので、
知らなくても実装は出来ますので、あまり興味のない方は、飛ばしても大丈夫です。
関数オブジェクトに保持される特別なプロパティ
関数もオブジェクトの一種なので、クラス同様にプロパティを持つことができます。
プロトタイプは、関数オブジェクトに自動的に保持されます。
// コンストラクタ関数を定義
function Sample(name) {
this.name = name
}
// プロトタイプのプロパティに値を設定
Sample.prop = 'adam'
console.log(Sample.prop) // adam
console.log(Sample) // 関数に自動的に「prototype: {}」が設定されている
プロトタイプオブジェクトにメソッドを登録
1つ目の特徴でみたプロトタイプオブジェクトに、関数を設定することができます。
その登録した関数は、インスタンスから実行可能です。
// コンストラクタ関数を定義
function Sample(name) {
this.name = name
}
// プロトタイプのprintプロパティにメソッドを設定
Sample.prototype.keisyo = 'さん'
Sample.prototype.print = function() {
console.log(`Hello, ${this.name}${Sample.prototype.keisyo}!`)
}
new Sample('adam').print() // Hello, adam!
インスタンス化する際に__proto__にコピーされる
new演算子によって、コンストラクタからインスタンスを作成する時、
コンストラクタ関数のプロトタイププロパティに存在するオブジェクトの参照が、
インスタンスの「__proto__」というプロパティにコピーされます。
// コンストラクタ関数を定義
function Sample(name) {
this.name = name
}
// プロトタイプのprintプロパティにメソッドを設定
Sample.prototype.keisyo = 'さん'
Sample.prototype.print = function() {
console.log(`Hello, ${this.name}${Sample.prototype.keisyo}!`)
}
const instance = new Sample('adam')
console.log(instance.__proto__)
console.log(instance.__proto__ === Sample.prototype) // true
最終行で、new演算子にて作成したインスタンスと
コンストラクタ関数のプロトタイプオブジェクトを比較しています。
結果は「true」となっており、コピーされていることが分かります。
また、インスタンス化したオブジェクトの「__proto__」に
コピーされた関数を呼び出したいときは、以下のように記述することで取得できます。
instance.__proto__.print() // Hello, undefinedさん!
instance.print() // Hello, adamさん!
ここで、注目していただきたいのは、
「__proto__」でインスタンスの「this.name」が、undifindになっている点です。
これは、「__proto__」にコピーされたプロトタイプオブジェクトには、
「name」プロパティが、存在しないため、undifindになっています。
そのため、インスタンス自体の「name」プロパティを見る必要があります。
プロトタイプチェーン
ほぼすべてのオブジェクトは、「__proto__」プロパティを持っています。
※例外的に持っていない場合もあります。
また、オブジェクトが多階層になっている場合には、「__proto__」プロパティが
オブジェクト分、存在することになります。
このように、プロトタイプが連続的に存在している状況をプロトタイプチェーンと言います。
プロトタイプチェーンの特徴
プロトタイプチェーンの特徴として、主に以下の3つがあります。
1つ目は、ほぼすべてのオブジェクトに「__proto__」プロパティが存在すること。
2つ目は、オブジェクトのプロパティにアクセスするときに、
対象のプロパティが見つからない場合、
「__proto__」プロパティ内のプロパティを探しに行きます。
3つ目は、2つ目の特徴で探しに行ったプロパティ内にも見つからなければ、
「__proto__」プロパティ内の「__proto__」プロパティ内にあるプロパティを探します。
「Object.create」の活用
「Object.create」を使えば、次のようにプロトタイプチェーンを作成することができます。
const test1 = {
sample1: function sample1() {
console.log('sample1')
}
}
const test2 = Object.create(test1)
test2.sample2 = function sample2() {
console.log('sample2')
}
const test3 = Object.create(test2)
test3.sample3 = function sample3() {
console.log('sample3')
}
test3.sample1() // sample1
test3.sample2() // sample2
test3.sample3() // sample3
「console.log(test3)」を実行すると、
上図のとおり、プロトタイプに「Object.create」の引数で渡した
オブジェクトの戻り値が格納されていることが分かります。
また、「test3」から各メソッドを実行することが出来ることも確認できます。
最後に
ここまで、読んでいただきありがとうございます。
JavaScriptのクラスは、まだまだ奥が深いので、色々試して使用を把握すると面白いと思います。
また、仕様を知れば、実現できる機能も増えていくと思います。
コメント