Menu
日本語

JavaScriptのクラス入門|constructorとnewの使い方

JavaScriptのクラスの仕組みを、constructor・メソッド・インスタンスフィールド・ゲッター/セッターまで、実際の書き方を通して理解できるように解説します。

クラスはオブジェクトの設計図

JavaScript の class(クラス)は、あるタイプのオブジェクトの形と振る舞いを定義するための仕組みです。設計図を一度書いておけば、あとはそこから必要なだけインスタンスを生成できます。各インスタンスは独自のデータを持ちつつ、メソッドは共通のものを使い回します。

基本の形はこんな感じです。

index.js
Output
Click Run to see the output here.

class User { ... } がひな型(設計図)で、new User(...) でそこからインスタンスを作ります。各インスタンスは自分専用の nameemail を持ちますが、greet メソッドはクラス上に1つだけ定義されていて、全インスタンスで共有されます。

インスタンスは2つ、データも2セット、でもメソッドは1セット。要するにそれだけの話です。

constructor で各インスタンスを初期化する

constructor は特別なメソッドで、new でインスタンスが作られる瞬間に、そのインスタンスごとにちょうど1回だけ実行されます。役割は、できたばかりのオブジェクトを初期化すること。典型的には、引数を this にコピーしていく処理ですね。

index.js
Output
Click Run to see the output here.

コンストラクタ内の this は、今まさに生成されようとしている新しいインスタンスを指します。this.x = x と書くと、その特定のオブジェクトに x がセットされるというわけです。new Point(...) を呼ぶたびに this は別物になるので、xy もインスタンスごとに独立しています。

コンストラクタを書かなかった場合は、JavaScript が自動で空のコンストラクタを用意してくれます。なので、実際に初期化処理が必要なときだけ書けばOKです。

new があってこそクラスは動く

new を付けずにクラスを呼び出すとエラーになります。

const p = Point(3, 4);
// TypeError: Class constructor Point cannot be invoked without 'new'

これはわざとそうなっています。new は次の4つを順番に実行します。

  1. 空のオブジェクトを新しく作る。
  2. そのオブジェクトをクラスのプロトタイプ(メソッドが定義されている場所)にリンクする。
  3. this を新しいオブジェクトにバインドした状態で constructor を実行する。
  4. 出来上がったオブジェクトを返す。

new を付けないと、これらは一切行われません。クラスはこのルールを強制してくれるので、うっかり書き忘れることがありません。class が導入される前は、new の付け忘れでグローバルオブジェクトが静かに汚染されてしまうことがあったので、これは大きな改善と言えます。

メソッドは共有、フィールドはインスタンスごと

クラス本体の中で定義したメソッドはプロトタイプ上に置かれ、すべてのインスタンスで1つのコピーが共有されます。一方でインスタンスフィールド(this に代入するもの)はインスタンスごとに別々のコピーを持ちます。

index.js
Output
Click Run to see the output here.

どちらのインスタンスもそれぞれ自分の count を持つので、片方をインクリメントしてももう片方には影響しません。一方で、a.incrementb.increment は文字通り同じ関数です。プロトタイプに一度だけ保存されていて、インスタンス経由で呼び出すたびにそこを参照しているだけなんですね。クラスのコストが抑えられるのはこのおかげで、インスタンスが1000個あってもメソッドが1000個コピーされるわけではありません。

クラスフィールド

インスタンスのフィールドは、コンストラクタの外側、クラス本体の先頭でまとめて宣言できます。

index.js
Output
Click Run to see the output here.

フィールド宣言は、あたかも constructor の先頭に書かれているかのように、コンストラクタ本体よりも先に実行されます。初期値がコンストラクタの引数に依存しない場合にこそ便利で、this.count = 0; this.step = 1; のような記述でコンストラクタをごちゃごちゃさせずに済みます。

一方、初期値が引数に依存する場合は、引数がスコープ内にあるコンストラクタの中で代入するようにしましょう。

ゲッターとセッター (getter / setter)

ゲッターやセッターは、見た目はメソッドですが、プロパティアクセスのように振る舞います。カッコを付けずに値を読み書きするだけで、裏側でメソッドが実行される仕組みです。

index.js
Output
Click Run to see the output here.

呼び出し側に注目してください。t.fahrenheit(カッコなし)で getter が呼ばれ、t.fahrenheit = 100 で setter が発動します。外から見ると fahrenheit はただのプロパティのようですが、実際は celsius からその都度計算されています。

getter は派生値の取得に向いていますし、setter は代入時のバリデーションや正規化にうってつけです。ただし多用は禁物です。重い処理をする getter を用意してしまうと、「プロパティにアクセスしただけ」のつもりが裏で実処理が走っていて、読み手を驚かせることになります。

メソッドの短縮記法、計算プロパティ名、そして this

クラス内のメソッド定義にはショートハンド構文を使います。function キーワードも不要ですし、名前と本体の間に : も書きません。

index.js
Output
Click Run to see the output here.

add から this を返しておくと、メソッドチェーンが使えるようになります。呼び出しごとに同じインスタンスを返すので、その結果に対して次のメソッドをそのまま呼べる、という仕組みです。

ここで一つ注意点があります。メソッドはインスタンスに自動でバインドされるわけではありません。メソッドを取り出して単独で呼び出すと、this は失われてしまいます。

index.js
Output
Click Run to see the output here.

g.hello() がちゃんと動くのは、.this を渡してくれるからです。一方、fn() のように単体で呼び出すとそうはいきません。イベントハンドラーなどでよくあるように、メソッドをあらかじめバインドして切り離して使いたい場合は、コンストラクター内で bind するか、アロー関数のクラスフィールドとして定義しましょう:hello = () => \Hi, ${this.name}`;`

実際に動くサンプルコード

ここまでのパーツ(フィールド、コンストラクター、メソッド、ゲッター)をまとめてみましょう。

index.js
Output
Click Run to see the output here.

見ての通り、BankAccount が何を表していて、何ができるのかが一目で分かりますね。

クラスの正体は実は関数

ここで押さえておきたいポイントがひとつ。class はあくまでシンタックスシュガーにすぎません。内部的に見ると、クラスは関数、もっと正確に言えばコンストラクタ関数であり、そのメソッドは ClassName.prototype 上に定義されています。

index.js
Output
Click Run to see the output here.

typeof User"function" になりますし、greetUser.prototype に定義されています。インスタンスが greet を探すときはプロトタイプチェーンをたどって見つけに行く仕組みで、これは JavaScript の最初期からある挙動そのままです。クラス構文は、この仕組みをきれいに書くための糖衣構文にすぎません。

このイメージを持っておくと後が楽になります。継承や instanceof、プロトタイプチェーンのデバッグも、「クラスとは、プロトタイプにメソッドがぶら下がった関数なんだ」と思い出せば腑に落ちるはずです。

次回:継承について

ここまでは独立したクラスだけを扱ってきました。でも実際の設計では、クラスを階層的に組み立てることがほとんどです。Animal の一種としての DogUser の一種としての AdminUser といった具合ですね。これを実現するのが extendssuper で、次はそのあたりを見ていきます。

よくある質問

JavaScriptでクラスはどうやって作る?

classキーワードにクラス名を続けて、中にconstructorと必要なメソッドを書くだけです。インスタンスはnew ClassName(...)で生成します。例えばclass User { constructor(name) { this.name = name; } }と書いておいて、new User('Ada')のように呼び出します。

constructorって結局なにをしているの?

constructornew ClassName(...)を呼んだタイミングで一度だけ実行されるメソッドです。役割はシンプルで、受け取った引数をthisに代入してインスタンスを初期化すること。自分で書かなかった場合でも、JavaScriptが空のconstructorを自動的に用意してくれます。

クラスと関数って何が違うの?

実はクラスの中身は関数です。メソッドをプロトタイプにぶら下げたコンストラクタ関数に、使いやすい構文を被せたもの、と考えるとわかりやすいです。ただしclassnewなしで呼ぶとエラーになる、extendsで継承がきれいに書ける、メソッドが列挙不可になる、といった違いもあります。新しく書くコードでは、昔ながらのコンストラクタ関数よりclassを使うのがおすすめです。

Coddyでコードを学ぼう

始める