オブジェクト指向プログラミングでは、クラスは、オブジェクトを作成するための拡張可能なプログラムコードテンプレートであり、状態(メンバ変数)の初期値と動作(メンバ関数またはメソッド)の実装を提供します。
実際には、ユーザー、商品など、同じ種類のオブジェクトを多数作成する必要があることがよくあります。
すでにコンストラクタ、演算子 "new"の章で学んだように、new function
が役立ちます。
しかし、現代のJavaScriptには、オブジェクト指向プログラミングに役立つ優れた新機能を紹介する、より高度な「クラス」構造があります。
「クラス」構文
基本的な構文は次のとおりです。
class MyClass {
// class methods
constructor() { ... }
method1() { ... }
method2() { ... }
method3() { ... }
...
}
次に、new MyClass()
を使用して、リストされたすべてのメソッドを持つ新しいオブジェクトを作成します。
constructor()
メソッドは new
によって自動的に呼び出されるため、そこでオブジェクトを初期化できます。
例:
class User {
constructor(name) {
this.name = name;
}
sayHi() {
alert(this.name);
}
}
// Usage:
let user = new User("John");
user.sayHi();
new User("John")
が呼び出されたとき
- 新しいオブジェクトが作成されます。
constructor
は与えられた引数で実行され、それをthis.name
に割り当てます。
…その後、user.sayHi()
のようなオブジェクトメソッドを呼び出すことができます。
初心者開発者が陥りやすい一般的な落とし穴は、クラスメソッド間にカンマを置くことです。これは構文エラーになります。
ここでの表記法をオブジェクトリテラルと混同しないでください。クラス内では、カンマは必要ありません。
クラスとは何ですか?
では、class
は一体何なのでしょうか?それは、人が考えるほど完全に新しい言語レベルのエンティティではありません。
どんな魔法も解き明かし、クラスが実際には何なのかを見てみましょう。それは、多くの複雑な側面を理解するのに役立ちます。
JavaScriptでは、クラスは一種の関数です。
こちらをご覧ください。
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// proof: User is a function
alert(typeof User); // function
class User {...}
構文が実際にすることは次のとおりです。
- クラス宣言の結果となる
User
という名前の関数を作成します。関数コードは、constructor
メソッドから取得されます(そのようなメソッドを記述しない場合は空とみなされます)。 sayHi
のようなクラスメソッドをUser.prototype
に格納します。
new User
オブジェクトが作成された後、そのメソッドを呼び出すと、F.prototypeの章で説明したように、プロトタイプから取得されます。したがって、オブジェクトはクラスメソッドにアクセスできます。
class User
宣言の結果を次のように図示できます。
それを詳しく調べるためのコードを次に示します。
class User {
constructor(name) { this.name = name; }
sayHi() { alert(this.name); }
}
// class is a function
alert(typeof User); // function
// ...or, more precisely, the constructor method
alert(User === User.prototype.constructor); // true
// The methods are in User.prototype, e.g:
alert(User.prototype.sayHi); // the code of the sayHi method
// there are exactly two methods in the prototype
alert(Object.getOwnPropertyNames(User.prototype)); // constructor, sayHi
単なる糖衣構文ではない
class
は「糖衣構文」(読みやすくするために設計されたが、新しいものを導入しない構文)であると言う人がいます。これは、class
キーワードをまったく使用せずに同じことを宣言できるためです。
// rewriting class User in pure functions
// 1. Create constructor function
function User(name) {
this.name = name;
}
// a function prototype has "constructor" property by default,
// so we don't need to create it
// 2. Add the method to prototype
User.prototype.sayHi = function() {
alert(this.name);
};
// Usage:
let user = new User("John");
user.sayHi();
この定義の結果はほぼ同じです。したがって、class
がコンストラクタとそのプロトタイプメソッドを定義するための糖衣構文と見なせる理由が確かにあります。
それでも、重要な違いがあります。
-
まず、
class
によって作成された関数には、特別な内部プロパティ[[IsClassConstructor]]: true
がラベル付けされます。したがって、手動で作成するのとまったく同じではありません。言語は、さまざまな場所でそのプロパティをチェックします。たとえば、通常の関数とは異なり、
new
で呼び出す必要があります。class User { constructor() {} } alert(typeof User); // function User(); // Error: Class constructor User cannot be invoked without 'new'
また、ほとんどのJavaScriptエンジンのクラスコンストラクタの文字列表現は、「class…」で始まります。
class User { constructor() {} } alert(User); // class User { ... }
他にも違いがあります。すぐにそれらを見ていきます。
-
クラスメソッドは列挙可能ではありません。クラス定義は、
"prototype"
内のすべてのメソッドに対してenumerable
フラグをfalse
に設定します。オブジェクトに対して
for..in
を実行する場合、通常はクラスメソッドは必要ないため、これは適切です。 -
クラスは常に
use strict
を使用します。クラス構文内のすべてのコードは、自動的に厳格モードになります。
さらに、class
構文は、後で説明する他の多くの機能をもたらします。
クラス式
関数と同様に、クラスも別の式内で定義したり、渡したり、返したり、代入したりできます。
クラス式の例を次に示します。
let User = class {
sayHi() {
alert("Hello");
}
};
名前付き関数式と同様に、クラス式にも名前を付けることができます。
クラス式に名前がある場合、それはクラス内でのみ表示されます。
// "Named Class Expression"
// (no such term in the spec, but that's similar to Named Function Expression)
let User = class MyClass {
sayHi() {
alert(MyClass); // MyClass name is visible only inside the class
}
};
new User().sayHi(); // works, shows MyClass definition
alert(MyClass); // error, MyClass name isn't visible outside of the class
次のように、オンデマンドでクラスを動的に作成することもできます。
function makeClass(phrase) {
// declare a class and return it
return class {
sayHi() {
alert(phrase);
}
};
}
// Create a new class
let User = makeClass("Hello");
new User().sayHi(); // Hello
ゲッター/セッター
リテラルオブジェクトと同様に、クラスにはゲッター/セッター、計算されたプロパティなどを組み込むことができます。
get/set
を使用して実装された user.name
の例を次に示します。
class User {
constructor(name) {
// invokes the setter
this.name = name;
}
get name() {
return this._name;
}
set name(value) {
if (value.length < 4) {
alert("Name is too short.");
return;
}
this._name = value;
}
}
let user = new User("John");
alert(user.name); // John
user = new User(""); // Name is too short.
技術的には、このようなクラス宣言は、User.prototype
にゲッターとセッターを作成することで機能します。
計算された名前 [...]
ブラケット [...]
を使用して計算されたメソッド名を使用した例を次に示します。
class User {
['say' + 'Hi']() {
alert("Hello");
}
}
new User().sayHi();
このような機能は、リテラルオブジェクトの機能を思い起こさせるため、覚えやすいです。
クラスフィールド
クラスフィールドは、言語に最近追加されたものです。
以前は、クラスにはメソッドしかありませんでした。
「クラスフィールド」は、任意のプロパティを追加できる構文です。
たとえば、name
プロパティを class User
に追加してみましょう。
class User {
name = "John";
sayHi() {
alert(`Hello, ${this.name}!`);
}
}
new User().sayHi(); // Hello, John!
したがって、"
クラスフィールドの重要な違いは、それらが User.prototype
ではなく個々のオブジェクトに設定されることです。
class User {
name = "John";
}
let user = new User();
alert(user.name); // John
alert(User.prototype.name); // undefined
より複雑な式と関数呼び出しを使用して値を代入することもできます。
class User {
name = prompt("Name, please?", "John");
}
let user = new User();
alert(user.name); // John
クラスフィールドを使用してバインドされたメソッドを作成する
関数バインディングの章で説明したように、JavaScriptの関数には動的な this
があります。それは、呼び出しのコンテキストによって異なります。
したがって、オブジェクトメソッドが渡され、別のコンテキストで呼び出された場合、this
はもはやそのオブジェクトへの参照にはなりません。
たとえば、このコードは undefined
を表示します。
class Button {
constructor(value) {
this.value = value;
}
click() {
alert(this.value);
}
}
let button = new Button("hello");
setTimeout(button.click, 1000); // undefined
この問題は、「this
を失う」と呼ばれます。
この問題を修正するには、関数バインディングの章で説明されているように、2つのアプローチがあります。
setTimeout(() => button.click(), 1000)
のようなラッパー関数を渡します。- コンストラクタ内などで、メソッドをオブジェクトにバインドします。
クラスフィールドは、別の非常にエレガントな構文を提供します。
class Button {
constructor(value) {
this.value = value;
}
click = () => {
alert(this.value);
}
}
let button = new Button("hello");
setTimeout(button.click, 1000); // hello
クラスフィールド click = () => {...}
はオブジェクトごとに作成されます。各 Button
オブジェクトには個別の関数があり、その中の this
はそのオブジェクトを参照します。button.click
をどこにでも渡すことができ、this
の値は常に正しいものになります。
これは、ブラウザ環境、特にイベントリスナーで非常に役立ちます。
まとめ
基本的なクラス構文は次のようになります。
class MyClass {
prop = value; // property
constructor(...) { // constructor
// ...
}
method(...) {} // method
get something(...) {} // getter method
set something(...) {} // setter method
[Symbol.iterator]() {} // method with computed name (symbol here)
// ...
}
MyClass
は技術的には関数であり(constructor
として提供する関数)、メソッド、ゲッター、セッターは MyClass.prototype
に書き込まれます。
次の章では、継承やその他の機能を含め、クラスについてさらに詳しく学びます。
コメント
<code>
タグを使用し、数行の場合は、<pre>
タグで囲み、10 行を超える場合は、サンドボックスを使用します(plnkr、 jsbin、 codepen…)