Menu
日本語
Playgroundで試す

JavaScriptのプライベートフィールド(#構文)完全ガイド

#プレフィックスでクラスのフィールドやメソッドを本当の意味でプライベートにする方法。アンダースコア命名規則では不十分な理由も合わせて解説します。

アンダースコア規約の限界

長らく JavaScript には本物のプライベートフィールドが存在しませんでした。そのため、プロパティ名の先頭にアンダースコアを付けて「これは触らないでね」と暗黙に示す慣習が使われてきました。みんながそのルールを守ってくれることを祈るしかなかったわけです。

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

_count は一見プライベートに見えますが、実はそうではありません。呼び出し側から自由に読めるし、書き換えもできるし、削除までできてしまいます。アンダースコアは「ここは触らないでね」という貼り紙のようなもので、ドア自体は開けっ放しなんです。

モダン JavaScript では、この問題を本物のプライベートフィールドで解決しました。目印は # です。

# を付ければ本当にプライベートになる

宣言するときも、アクセスするときも、フィールド名の先頭に # を付けます:

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

クラスの内部からは this.#count を普通に使えますが、外部からはそもそも存在しないものとして扱われます。

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

# はフィールド名の一部そのものです。他の言語にある private のような修飾子キーワードではなく、パーサーがオブジェクト上の保護されたスロットを引くために使うシジル(記号)なんです。だからこそ、このエラーはコードが実行される前の、パース時点で出てきます。

プライベートメソッドとゲッター

# 構文をつけられるのはフィールドだけではありません。メソッド、ゲッター、セッターにもすべて # プレフィックスをつけられます:

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

#assertPositive は内部用のヘルパーです。公開APIの一部ではないので、本当の意味でプライベートにしておけば、外から誤って呼ばれることもなく、誰かに依存されることもありません。あとからリネームしたり削除したりするのも自由です。

プライベート static メンバー

static メンバーも private にできます。同じように # を先頭に付けるだけです。

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

プライベート static メンバーはインスタンスではなくクラス自体に紐づきます。クラス外に漏らしたくないカウンターやキャッシュ、設定値などを持たせるのに便利です。

サブクラスからはアクセスできない

ここは Java や C# から来た人がハマりやすいポイントです。JavaScript のプライベートフィールドは「インスタンス単位」ではなく「クラス単位」でプライベートになります。つまり、サブクラスから親クラスのプライベートフィールドにアクセスすることはできません。

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

JavaScript には protected という修飾子はありません。サブクラスから親のデータを使いたい場合は、親側でメソッドや getter を用意するか、(あまり一般的ではないですが)プライベートでないフィールドとして公開する必要があります。これは意図的な設計で、private は本当に private を意味し、継承であっても抜け道は作れないようになっています。

in 演算子によるプライベートフィールドの存在チェック

ときには、あるオブジェクトが本当に自分のクラスのインスタンスかどうかを確かめたい場面があります。いわゆる ブランドチェック です。クラス内部であれば、in 演算子にプライベートフィールド名を渡して判定できます。

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

#balance を持つオブジェクトを生成できるのは Wallet だけなので、#balance in objobj が正真正銘の Wallet インスタンスかどうかを確実に判定できます。プライベートフィールドは外部から偽装できないため、ケースによっては instanceof より高速かつ安全です。

よくある落とし穴:プレーンオブジェクトには存在しない

プライベートフィールドは、クラスのコンストラクタから生成されたインスタンスにのみ存在します。new を通さずに作ったオブジェクトに対して使おうとすると、例外が投げられます。

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

thisPoint のインスタンスでない状態でメソッドを呼ぶと、実行時にエラーになります。先ほど紹介したブランドチェックの正体がこれです。プライベートフィールドは「それっぽい形をしたオブジェクト」ではなく、宣言したクラスそのものに紐づいているのです。

# を使うべき場面

クラスの公開 API に含めないステートやヘルパーは、基本的にすべてプライベートフィールドにしてしまうのがおすすめです。理由は次のとおりです。

  • リファクタリングしやすい。 そもそも見えない内部実装に、呼び出し側が依存しようがありません。
  • 本物のカプセル化。 外部コードから誤って読み書きされたり削除されたりする心配がなくなります。
  • 補完がすっきりする。 エディタの補完候補に、外部向けではないメンバーが出てこなくなります。

本当にインターフェースの一部として公開したいものだけ、public プロパティにしましょう。プライベートフィールドを読み取り専用で外に出したいときは、ゲッター(get name())を使えば OK です。昔ながらのアンダースコア命名(_foo)はもう卒業で大丈夫。あれは言語機能が追いついていなかった時代の苦肉の策で、今の JavaScript にはちゃんとした # 構文があります。

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

#celsius が実際の値を持つ隠しストレージで、celsiusfahrenheit は読み取り専用のビューという位置づけです。呼び出し側から内部状態を書き換えられる心配はなく、後々クラス側で保持方法を変更する自由も残ります。

次は: プロトタイプ

クラス構文は、実のところ JavaScript のプロトタイプシステムに対するシンタックスシュガーにすぎません。プロトタイプこそが言語の土台にある、より古くて根本的なモデルです。これを理解すると、this があんな挙動をする理由も、継承が裏側で実際にどう動いているのかも、クラスの extends が内部で何をやっているのかも腑に落ちます。次のページで見ていきましょう。

よくある質問

JavaScriptでプライベートフィールドを宣言するには?

フィールド名の先頭に#を付けるだけです。宣言時もアクセス時も、毎回#が必要になります。たとえば class Counter { #count = 0; increment() { this.#count++; } } のように書くと、#countは本当の意味でプライベートになります。ポイントは、#は演算子ではなく名前の一部だということです。

#field_field は何が違う?

_field は「外から触らないでね」という命名規則にすぎないので、実際には普通のパブリックプロパティと同じで、外部から自由に読み書きできてしまいます。一方 #field は言語仕様で保護されていて、クラスの外からアクセスしようとするとパース時点でSyntaxErrorになります。本当にカプセル化したいときは#を使いましょう。

サブクラスから親クラスのプライベートフィールドにアクセスできる?

できません。プライベートフィールドは宣言したクラスの中だけに閉じていて、継承先のサブクラスからも触れない仕様です。サブクラスから使いたい場合は、親クラス側でメソッドやgetterを公開するしかありません。他言語のprotectedよりも厳しい挙動ですが、これは意図的な設計です。

オブジェクトが特定のプライベートフィールドを持っているか確認できる?

はい、クラス内でならin演算子が使えます。#field in obj と書くと、objがそのプライベートフィールドを持っていればtrueが返ります。メソッドを呼ぶ前に「これは本当にこのクラスのインスタンスか?」を確認する、いわゆるブランドチェックに便利です。

Coddyでコードを学ぼう

始める