序章
JavaScriptはプロトタイプベースの言語であり、JavaScriptのすべてのオブジェクトには、オブジェクトのプロパティとメソッドを拡張するために使用できるPrototypeと呼ばれる非表示の内部プロパティがあります。 プロトタイプの詳細については、JavaScriptでのプロトタイプと継承の理解チュートリアルをご覧ください。
最近まで、勤勉な開発者はコンストラクター関数を使用して、JavaScriptのオブジェクト指向デザインパターンを模倣していました。 言語仕様ECMAScript2015は、ES6と呼ばれることが多く、JavaScript言語にクラスを導入しました。 JavaScriptのクラスは実際には追加機能を提供せず、よりクリーンでエレガントな構文を提供するという点で、プロトタイプや継承よりも「シンタックスシュガー」を提供すると説明されることがよくあります。 他のプログラミング言語はクラスを使用するため、JavaScriptのクラス構文により、開発者は言語間を簡単に移動できます。
クラスは関数です
JavaScriptクラスは一種の関数です。 クラスはclassキーワードで宣言されます。 関数式構文を使用して関数を初期化し、クラス式構文を使用してクラスを初期化します。
// Initializing a function with a function expression
const x = function() {}
// Initializing a class with a class expression
const y = class {}
Object.getPrototypeOf()メソッドを使用して、オブジェクトのPrototypeにアクセスできます。 これを使用して、作成した空の関数をテストしてみましょう。
Object.getPrototypeOf(x);
Outputƒ () { [native code] }
作成したクラスでもそのメソッドを使用できます。
Object.getPrototypeOf(y);
Outputƒ () { [native code] }
functionとclassで宣言されたコードは、どちらも関数Prototypeを返します。 プロトタイプでは、newキーワードを使用して任意の関数をコンストラクターインスタンスにすることができます。
const x = function() {}
// Initialize a constructor from a function
const constructorFromFunction = new x();
console.log(constructorFromFunction);
Outputx {}
constructor: ƒ ()
これはクラスにも当てはまります。
const y = class {}
// Initialize a constructor from a class
const constructorFromClass = new y();
console.log(constructorFromClass);
Outputy {}
constructor: class
これらのプロトタイプコンストラクターの例は、それ以外は空ですが、構文の下で、両方のメソッドが同じ最終結果を達成していることがわかります。
クラスの定義
プロトタイプと継承のチュートリアルでは、テキストベースのロールプレイングゲームでのキャラクター作成に基づいた例を作成しました。 ここでその例を続けて、構文を関数からクラスに更新しましょう。
コンストラクター関数は、関数自体を参照してthisのプロパティとして割り当てられるいくつかのパラメーターで初期化されます。 識別子の最初の文字は、慣例により大文字になります。
コンストラクター.js
// Initializing a constructor function
function Hero(name, level) {
this.name = name;
this.level = level;
}
これを以下に示すclass構文に変換すると、非常によく似た構造になっていることがわかります。
class.js
// Initializing a class definition
class Hero {
constructor(name, level) {
this.name = name;
this.level = level;
}
}
コンストラクター関数は、初期化子の最初の文字(オプション)を大文字にし、構文に精通していることにより、オブジェクトの青写真になることを意味します。 classキーワードは、関数の目的をより簡単に伝えます。
初期化の構文の唯一の違いは、functionの代わりにclassキーワードを使用し、constructor()メソッド内でプロパティを割り当てることです。
メソッドの定義
コンストラクター関数の一般的な方法は、以下のgreet()メソッドに示すように、初期化ではなく、prototypeにメソッドを直接割り当てることです。
コンストラクター.js
function Hero(name, level) {
this.name = name;
this.level = level;
}
// Adding a method to the constructor
Hero.prototype.greet = function() {
return `${this.name} says hello.`;
}
クラスを使用すると、この構文が簡略化され、メソッドをクラスに直接追加できます。 ES6で導入されたメソッド定義の省略形を使用すると、メソッドの定義はさらに簡潔なプロセスになります。
class.js
class Hero {
constructor(name, level) {
this.name = name;
this.level = level;
}
// Adding a method to the constructor
greet() {
return `${this.name} says hello.`;
}
}
これらのプロパティとメソッドの動作を見てみましょう。 newキーワードを使用して、Heroの新しいインスタンスを作成し、いくつかの値を割り当てます。
const hero1 = new Hero('Varg', 1);
console.log(hero1)を使用して新しいオブジェクトに関する詳細情報を出力すると、クラスの初期化で何が起こっているかについての詳細を確認できます。
OutputHero {name: "Varg", level: 1}
__proto__:
▶ constructor: class Hero
▶ greet: ƒ greet()
constructor()およびgreet()関数が__proto__、またはhero1のPrototypeに適用され、 hero1オブジェクトのメソッドとして直接。 これはコンストラクター関数を作成するときは明らかですが、クラスを作成するときは明らかではありません。 クラスを使用すると、より単純で簡潔な構文が可能になりますが、プロセスの明確さがいくらか犠牲になります。
クラスの拡張
コンストラクター関数とクラスの利点は、親に基づいて新しいオブジェクトブループリントに拡張できることです。 これにより、類似しているが追加またはより具体的な機能が必要なオブジェクトのコードが繰り返されるのを防ぎます。
call()メソッドを使用して、親から新しいコンストラクター関数を作成できます。 以下の例では、Mageというより具体的な文字クラスを作成し、call()を使用してHeroのプロパティを割り当て、さらにプロパティを追加します。
コンストラクター.js
// Creating a new constructor from the parent
function Mage(name, level, spell) {
// Chain constructor with call
Hero.call(this, name, level);
this.spell = spell;
}
この時点で、Heroと同じプロパティと、追加した新しいプロパティを使用して、Mageの新しいインスタンスを作成できます。
const hero2 = new Mage('Lejon', 2, 'Magic Missile');
hero2をコンソールに送信すると、コンストラクターに基づいて新しいMageが作成されたことがわかります。
OutputMage {name: "Lejon", level: 2, spell: "Magic Missile"}
__proto__:
▶ constructor: ƒ Mage(name, level, spell)
ES6クラスでは、callの代わりにsuperキーワードを使用して親関数にアクセスします。 親クラスを参照するためにextendsを使用します。
class.js
// Creating a new class from the parent
class Mage extends Hero {
constructor(name, level, spell) {
// Chain constructor with super
super(name, level);
// Add a new property
this.spell = spell;
}
}
これで、同じ方法で新しいMageインスタンスを作成できます。
const hero2 = new Mage('Lejon', 2, 'Magic Missile');
hero2をコンソールに出力し、出力を表示します。
OutputMage {name: "Lejon", level: 2, spell: "Magic Missile"}
__proto__: Hero
▶ constructor: class Mage
クラス構成でPrototypeが親(この場合はHero)にリンクされていることを除いて、出力はほぼ同じです。
以下は、初期化、メソッドの追加、およびコンストラクター関数とクラスの継承のプロセス全体を並べて比較したものです。
コンストラクター.js
function Hero(name, level) {
this.name = name;
this.level = level;
}
// Adding a method to the constructor
Hero.prototype.greet = function() {
return `${this.name} says hello.`;
}
// Creating a new constructor from the parent
function Mage(name, level, spell) {
// Chain constructor with call
Hero.call(this, name, level);
this.spell = spell;
}
class.js
// Initializing a class
class Hero {
constructor(name, level) {
this.name = name;
this.level = level;
}
// Adding a method to the constructor
greet() {
return `${this.name} says hello.`;
}
}
// Creating a new class from the parent
class Mage extends Hero {
constructor(name, level, spell) {
// Chain constructor with super
super(name, level);
// Add a new property
this.spell = spell;
}
}
構文はまったく異なりますが、基本的な結果は両方のメソッド間でほぼ同じです。 クラスは、オブジェクトブループリントを作成するためのより簡潔な方法を提供し、コンストラクター関数は、内部で何が起こっているかをより正確に記述します。
結論
このチュートリアルでは、JavaScriptコンストラクター関数とES6クラスの類似点と相違点について学びました。 クラスとコンストラクターはどちらも、オブジェクト指向の継承モデルを、プロトタイプベースの継承言語であるJavaScriptに模倣します。
プロトタイプの継承を理解することは、効果的なJavaScript開発者であるために最も重要です。 React などの一般的なJavaScriptライブラリはclass構文を頻繁に使用するため、クラスに精通していると非常に役立ちます。