ES6におけるクラス(class)の内部原理を深く理解する

ES6では、JavaScriptのオブジェクト指向プログラミングスタイルをより簡潔で理解しやすくするクラス(class)が導入されました。クラスは他のプログラミング言語における従来のオブジェクト指向モデルのように見えますが、その内部は依然としてJavaScriptの既存のプロトタイプ継承メカニズムに基づいています。この記事では、ES6クラスの内部原理を詳しく解説し、その動作メカニズムを理解する手助けをします。

1. クラスの本質はコンストラクタ

ES6で定義されたクラスは、実際にはコンストラクタです。クラスの本質を typeof キーワードで確認できます:

1
class Person {
2
constructor(name) {
3
this.name = name;
4
}
5
}
6
7
console.log(typeof Person); // "function"

クラスの定義は最終的に関数にコンパイルされます。つまり、クラスは単なるコンストラクタの糖衣構文です。

2. コンストラクタの動作原理

クラス内の constructor メソッドは、クラスインスタンスを初期化するためのコンストラクタです。インスタンスを作成する際、new キーワードでこのコンストラクタが呼び出されます:

1
class Person {
2
constructor(name) {
3
this.name = name;
4
}
5
}
6
7
const p = new Person("Alice");
8
console.log(p.name); // "Alice"

これは、従来のES5の関数コンストラクタと非常に似ています:

1
function Person(name) {
2
this.name = name;
3
}
4
5
const p = new Person("Alice");
6
console.log(p.name); // "Alice"

3. プロトタイプ継承

クラス内のメソッドは、そのプロトタイプに定義されます。これはES5のプロトタイプ継承メカニズムと完全に一致します。クラスのメソッドを定義すると、そのメソッドはクラスのプロトタイプに追加されます:

1
class Person {
2
constructor(name) {
3
this.name = name;
4
}
5
6
greet() {
7
console.log(`Hello, ${this.name}!`);
8
}
9
}
10
11
const p = new Person("Alice");
12
p.greet(); // "Hello, Alice!"

ES5では、同じ効果を以下のように実現します:

1
function Person(name) {
2
this.name = name;
3
}
4
5
Person.prototype.greet = function () {
6
console.log(`Hello, ${this.name}!`);
7
};
8
9
const p = new Person("Alice");
10
p.greet(); // "Hello, Alice!"

クラスの内部継承メカニズムは、依然としてプロトタイプチェーンを介して実現されています。

4. クラスの継承メカニズム

ES6では extends キーワードを使用してクラスの継承をサポートしていますが、これはJavaScriptの既存のプロトタイプ継承に依存しています。サブクラスが親クラスを継承する際、サブクラスのプロトタイプは親クラスのプロトタイプを指し、これによりメソッドの継承が実現されます:

1
class Animal {
2
constructor(name) {
3
this.name = name;
4
}
5
6
speak() {
7
console.log(`${this.name} makes a noise.`);
8
}
9
}
10
11
class Dog extends Animal {
12
speak() {
13
console.log(`${this.name} barks.`);
14
}
15
}
16
17
const d = new Dog("Rex");
18
d.speak(); // "Rex barks."

これはES5におけるプロトタイプチェーンを手動で実装することに相当します:

1
function Animal(name) {
2
this.name = name;
3
}
4
5
Animal.prototype.speak = function () {
6
console.log(`${this.name} makes a noise.`);
7
};
8
9
function Dog(name) {
10
Animal.call(this, name);
11
}
12
13
Dog.prototype = Object.create(Animal.prototype);
14
Dog.prototype.constructor = Dog;
15
16
Dog.prototype.speak = function () {
17
console.log(`${this.name} barks.`);
18
};
19
20
const d = new Dog("Rex");
21
d.speak(); // "Rex barks."

5. super を使用した親クラスメソッドの呼び出し

super キーワードは、サブクラスから親クラスのコンストラクタやメソッドを呼び出すことができます。サブクラスのコンストラクタ内で super は親クラスのコンストラクタを呼び出し、親クラスの属性やメソッドを継承します:

1
class Animal {
2
constructor(name) {
3
this.name = name;
4
}
5
}
6
7
class Dog extends Animal {
8
constructor(name, breed) {
9
super(name); // 親クラスのコンストラクタを呼び出す
10
this.breed = breed;
11
}
12
}
13
14
const d = new Dog("Rex", "Labrador");
15
console.log(d.name); // "Rex"
16
console.log(d.breed); // "Labrador"

ES5では、親クラスのコンストラクタを明示的に呼び出して、同様の効果を実現できます:

1
function Animal(name) {
2
this.name = name;
3
}
4
5
function Dog(name, breed) {
6
Animal.call(this, name); // 親クラスのコンストラクタを呼び出す
7
this.breed = breed;
8
}
9
10
const d = new Dog("Rex", "Labrador");
11
console.log(d.name); // "Rex"
12
console.log(d.breed); // "Labrador"

まとめ

ES6クラスの導入により、JavaScriptのオブジェクト指向プログラミングがより簡潔な構文で書けるようになりましたが、その内部は依然としてコンストラクタとプロトタイプチェーンメカニズムに依存しています。クラスは実際にはコンストラクタの糖衣構文であり、クラスの継承メカニズムもプロトタイプチェーンに基づいています。superを使用することで、サブクラスが親クラスのコンストラクタやメソッドを呼び出し、継承関係を実現します。クラスの内部原理を理解することで、JavaScriptのオブジェクト指向特性をより効果的に活用できるようになります。