staticメンバーはクラスそのものに属する
クラスに書くメソッドのほとんどは、特定のインスタンス――たとえばある user やある circle ――に対して動きます。ところが static メンバーはちょっと違っていて、インスタンスではなくクラス自体に紐づきます。呼び出すときもインスタンス経由ではなく、クラス名から直接呼び出します。
double は MathUtils 自体に属していて、インスタンスには存在しません。インスタンスを作っても意味はなく、m.double は undefined になります。これは通常のメソッドとは真逆の挙動です。通常のメソッドはインスタンス側(正確にはプロトタイプ)に存在していて、クラス自身からは見えません。
イメージとしてはこう捉えるとわかりやすいです。class キーワードは2つの入れ物を同時に作ります。ひとつは new MathUtils() で使うインスタンスメソッド群、もうひとつは MathUtils から直接呼び出す static メソッド群です。static キーワードは、そのメンバーをどちらの入れ物に入れるかを指定しているだけ、というわけです。
定番の使い方:ファクトリメソッド
javascript で static メソッドを書く一番よくある理由が、もうひとつのコンストラクタ(ファクトリメソッド)を用意したいケースです。本来の constructor は決まった引数を受け取りますが、実際の開発ではオブジェクトの作り方を複数用意したい場面がよくあります。JSON から、DB の1行から、URL から……といった具合です。
fromJSON はユーザーを 使う のではなく、ユーザーを 生成する メソッドです。こういうケースこそ static メソッドの出番です。代わりに parseUser のような単独の関数を定義することもできますが、クラスに持たせておけば関連する処理がひとまとまりになり、呼び出し側から見ても意図がはっきりします。
static プロパティ
クラス自体にデータを持たせることもできます。
Circle.PI はすべての円インスタンスで共有されます。クラスに属する定数なので、インスタンスごとにコピーされるわけではありません。インスタンスメソッド内から参照するときは this 経由ではなく、クラス名を使って Circle.PI と書く点に注意してください。
static プロパティは、設定値、インスタンス間で共有するキャッシュ、カウンター、クラスレベルの定数などに便利です。
staticメソッド内の this はクラスそのもの
通常のメソッドでは this はインスタンスを指しますが、staticメソッドの中では this はクラス自体を指します。
increment の中の this.count は Counter.count を指します。最初は違和感があるかもしれませんが、これこそが static メソッドの継承をうまく機能させている仕組みです。this はそのメソッドを定義したクラスではなく、メソッドを呼び出したクラスを参照します。
staticメソッドと継承
staticメソッドはサブクラスにも継承されます。さらに this が呼び出し元のクラスを指すおかげで、ファクトリメソッドは自動的に適切なサブクラスのインスタンスを返してくれます。
Animal.create の中では new this(name) と書いています。Dog.create("Rex") と呼び出したとき、this は Dog を指すので new this(name) は Dog のインスタンスを生成します。ここを new Animal(name) と書いてしまうと、常に Animal が作られてしまい、このパターンが成り立ちません。これこそが、staticメソッド内の this がクラス自身を指すように設計されている大きな理由です。
staticメソッドとインスタンスメソッドを比べてみる
同じような処理を、この2つのスタイルでそれぞれ書き分けてみましょう。
どちらも計算の中身は同じです。インスタンス版は this.celsius からデータを取り出し、static 版は引数として受け取ります。使い分けの目安としては、「このオブジェクトが行う操作」ならインスタンスメソッド、「入力さえ渡せばこのクラスが計算できるもの」なら static メソッド、と考えるとわかりやすいです。
初期化処理に便利な static ブロック
static プロパティの初期化では、単純な式一つでは足りない場面もあります。ループを回したり、条件分岐を入れたり、複数の値が絡み合って依存していたり…。そんなときに出番になるのが static ブロックです。
static { ... } ブロックは、クラスが定義されたタイミングで一度だけ実行されます。このブロックの中では、this はクラス自身を指します。複数ステップの初期化をまとめたいときに便利ですが、値を1つ代入するだけなら、普通の static フィールドを使った方がスッキリします。
プライベートな static メンバ
static フィールドは、# プレフィックスを付ければプライベートにもできます。クラスの内部からしかアクセスできません。
#nextId はクラスの内側に閉じ込められているので、外のコードから IdGenerator.next() を呼ぶことはできても、カウンター自体を読んだりリセットしたりはできません。プライベートフィールドについては近いうちに専用のページで扱いますが、static と # を組み合わせられるということはここで押さえておきましょう。
static を使わないほうがいい場面
staticメソッドはヘルパーをまとめる手段として便利ですが、だからといってユーティリティを片っ端からクラスにする理由にはなりません。独立した関数がずらっと並んだファイルがあるなら、関数をそのままエクスポートしてください。名前空間を切りたいだけの目的で全部staticメソッドにしたクラスでくるむのはやめたほうがいいです。モジュール自体がすでにその役割を担っていますし、そのほうが見通しもすっきりします。
static を選ぶのはこんなときです。
- 関数が本当にそのクラスに属している場合(そのクラスに対するファクトリメソッド、コンバーター、バリデーターなど)。
- クラスの全インスタンスで共有したい状態がある場合。
- サブクラスでオーバーライドしたり継承したりする可能性がある場合。
それ以外なら、素の関数のほうがシンプルな選択肢です。
次のテーマ: プライベートフィールド
先ほど少しだけ登場した #nextId は、JavaScript のプライベートフィールド構文です。インスタンスメンバーにも static メンバーにも使えて、クラス内部の実装の詳細を隠すための今風のやり方です。次回はこれを取り上げます。
よくある質問
JavaScriptのstaticメソッドとは何ですか?
staticメソッドは、インスタンスではなくクラス自身に紐づくメソッドです。staticキーワードを付けて定義し、MyClass.doThing()のようにクラスから直接呼び出します。インスタンスからthis.doThing()で呼ぶことはできず、あくまでクラス経由でのアクセスになります。
staticメソッドとインスタンスメソッドはどう使い分ければいいですか?
そのクラスに関連はあるけれど、特定のインスタンスの状態を読んだり書き換えたりする必要がない処理はstaticメソッド向きです。例えばUser.fromJSON(...)のようなファクトリメソッド、Math.maxのようなユーティリティ関数、クラスを名前空間として使う定数などですね。逆に、thisでインスタンスを参照したいならインスタンスメソッドにします。
staticメソッドからインスタンスのプロパティにアクセスできますか?
直接はアクセスできません。staticメソッド内のthisはクラス自身を指すので、this.nameと書くとインスタンスのフィールドではなくstaticプロパティを読むことになります。インスタンスのデータを使いたい場合は、static summarize(user) { return user.name; }のように引数として渡すのが基本です。
JavaScriptのstaticメソッドは継承されますか?
はい、継承されます。サブクラスが親クラスをextendsすると、親のstaticメソッドはサブクラス側でもそのまま呼び出せます。staticメソッド内のthisは、実際に呼び出したクラスを指すため、サブクラスからファクトリメソッドを呼んでも期待通りに動作します。