こんにちは!日々のフロントエンド開発やNode.jsでのバックエンド構築、本当にお疲れ様です。
JavaScriptを学び進めていくと、「データの隠蔽(カプセル化)」、つまり「外から見られたくない大切なデータを、どうやって安全に守るか」という壁に必ずぶつかりますよね。他のオブジェクト指向言語(JavaやC++、TypeScriptなど)を触ったことがある方なら、「`private`修飾子みたいに、クラスの中でしか触れないようにしたい!」と思うはずです。
実は、長年の間、JavaScriptにはそれを実現する綺麗な仕組みがなく、開発者たちは知恵を絞って「クロージャ」というテクニックでそれを模倣してきました。しかし、現代のモダンなJavaScriptには、ランタイムレベルで完全にデータを守り抜く「プライベートクラスフィールド(`#`構文)」が標準装備されています。
ここをクリアすれば、あなたのクラス設計は一気にプロフェッショナルなものになります。V8エンジンのメモリ構造やスコープの裏側まで見渡せるような、真のカプセル化の世界へ一緒に踏み出してみましょう!
—
1. 昔はどうやって隠蔽していた? —— クロージャによるカプセル化の限界
プライベートクラスフィールドが登場する前、JavaScriptで「外部から見えない変数」を作ろうとしたら、決まってクロージャを使っていました。
まずは、懐かしの(そして今でもたまに見かける)クロージャを使ったクラス風の書き方を見てみましょう。
function createCounter() {
// この変数 count は、createCounter のスコープの中に閉じ込められている(=外から直接触れない)
let count = 0;
return {
increment() {
count++;
console.log(`現在のカウント: ${count}`);
},
getCount() {
return count;
}
};
}
const counter = createCounter();
counter.increment(); // “現在のカウント: 1”
console.log(counter.count); // ➡️ undefined (外部から直接 count にはアクセスできない!)
この方法は確かにデータを隠蔽できていますが、いくつか大きなデメリットがありました。
1. クラス構文(`class`)が使えないため、オブジェクト指向的なデザインパターン(継承など)と相性が悪い。
2. インスタンスを生成するたびに、メソッド(`increment` や `getCount`)がメモリ上の別領域に複製されがちになり、V8エンジンのヒープメモリを無駄に圧迫する(プロトタイプチェーンの恩恵を受けにくい)。
3. 「アンダースコア(`_`)から始める」という命名規則に頼るだけで、言語仕様としては普通に外からアクセスできてしまう(「触っちゃダメ絶対」というエンジニア間のマナーに依存している状態でした)。
—
2. 救世主登場!プライベートクラスフィールド(`#`)の使い方
ES2022で正式に導入されたプライベートクラスフィールドは、識別子の先頭にシャープ(`#`)を付けるだけで、その変数が「クラスの外からは絶対に触れない真のプライベート」であることをJavaScriptエンジンに伝える魔法の構文です。
実際の書き方を、先ほどのカウンターを例にモダンな `class` で書いてみましょう。
class SecureCounter {
// # を付けることで、V8エンジンのレベルで外部からのアクセスが完全に遮断されます
#count = 0;
increment() {
this.#count++;
console.log(`セキュアなカウント: ${this.#count}`);
}
getCount() {
return this.#count;
}
}
const myCounter = new SecureCounter();
myCounter.increment(); // “セキュアなカウント: 1”
// 外部から直接 #count にアクセスしようとすると…?
// console.log(myCounter.#count);
// ➡️ SyntaxError: Private field ‘#count’ must be declared in an enclosing class
なんと、外部から `#count` にアクセスしようとした瞬間に、SyntaxError(構文エラー)が発生します。TypeScriptの `private` のように「コンパイル時やIDEが警告してくれるだけ」ではなく、JavaScriptのランタイムそのものがそのアクセスをバツっと拒絶するのです。これが「真のスコープ隔離」と呼ばれる所以ですね。
—
3. コードの裏側で何が起きているのか?(V8エンジンの視点)
ここで少しだけ踏み込んで、この `#` がJavaScriptの裏側(ランタイム)でどう扱われているかを知っておきましょう。
他の言語の `private` は、多くの場合「コンパイルが終わると消える(型情報の話)」ものですが、JavaScriptのプライベートフィールドはランタイム(実行時)に厳格に管理されるメモリ上のプライベートスロットとして存在します。
イメージ図:
[ SecureCounter のインスタンス (メモリ空間) ]
├── [[Prototype]] —> SecureCounter.prototype
└── [[PrivateSlots]]
└── #count —> 0 (※外からはこのスロットの存在すら覗き見できない)
V8エンジンは、インスタンスごとに隠されたプライベートスロットを用意し、そのクラスのメソッド(同じスコープを共有するコード)からしかそのスロット番号を引けない仕組みにしています。そのため、プロトタイプチェーンをどう悪用しようとも、リフレクションでこじ開けようとも、外から `#count` の値を盗み見ることは物理的に不可能です。
—
4. 陥りやすい文法エラーと注意すべきポイント
プライベートフィールドは非常に強力ですが、初学者の皆さんが「あれっ?」とハマりがちな罠がいくつかあります。ここで事前に押さえておきましょう。
罠1: 事前宣言(フィールド宣言)が必須です
通常のプロパティであれば、コンストラクタ(`constructor`)の中でいきなり `this.foo = 123` のように書き始めても動きますよね。しかし、プライベートフィールドはクラスのトップレベル(コンストラクタの外側)で事前に宣言しておく必要があります。
class BadExample {
constructor() {
// ❌ 宣言せずにコンストラクタ内でいきなり #ng を使うとエラーになります!
// this.#ng = 100;
}
#ok = 100; // ⭕ 必ずここに書く必要があります
}
罠2: クラスの「外」だけでなく、「サブクラス(継承先)」からも見えません
オブジェクト指向を学んでいると、「継承した子クラスなら親のプライベート変数くらい触れるだろう」と思いがちですが、JavaScriptのプライベートフィールドは親クラス専用です。子クラスから親の `#` フィールドを直接触ることはできません。
class Parent {
#secret = “秘密のデータ”;
}
class Child extends Parent {
reveal() {
// ❌ エラー! 子クラスであっても親のプライベートフィールドにはアクセス不可
// console.log(this.#secret);
}
}
「じゃあどうやって子クラスにデータを渡すの?」という場合は、親クラス側で公開しているパブリックなメソッド(getterなど)を経由するか、後述するメソッドのプライベート化などを組み合わせる設計が必要になります。
—
5. プライベートメソッドやプライベートgetter/setterもあるよ!
実は変数(フィールド)だけでなく、メソッド自体やgetter/setterにも `#` を付けることができます。
例えば、クラス内部でしか使わない複雑なバリデーション処理などを隠蔽したいときに絶大な効果を発揮します。
class UserAccount {
#username;
#passwordHash;
constructor(username, rawPassword) {
this.#username = username;
// 内部のプライベートメソッドを呼び出してハッシュ化(擬似)
this.#passwordHash = this.#hashPassword(rawPassword);
}
// 🔒 プライベートメソッド
#hashPassword(password) {
return `hashed_${password}_secure_salt`;
}
// 外部に安全に公開するインターフェース (Getter)
get username() {
return this.#username;
}
// パスワードの正誤判定だけを公開するメソッド
verifyPassword(inputPassword) {
return this.#passwordHash === this.#hashPassword(inputPassword);
}
}
const user = new UserAccount(“Alice”, “mypass123”);
console.log(user.username); // “Alice”
console.log(user.verifyPassword(“mypass123”)); // true
// ❌ 外部から #hashPassword を呼ぶことはできません
// user.#hashPassword(“test”); -> SyntaxError
外部のコードは「ユーザー名が取れること」と「パスワードがあっているか判定できること」だけを知っていればよく、パスワードをどうやってハッシュ化しているかという「実装の詳細(ドメインの秘密)」は完全にクラスの内側に閉じ込められています。これが、保守性の高い堅牢なソフトウェアを組むためのカプセル化の本質です。
—
まとめ:モダンなクラス設計の主役にしよう
今回は、プライベートクラスフィールド(`#`)が実現する真のスコープ隔離について、クロージャとの比較やV8エンジンの挙動を交えて解説しました。
- 昔はクロージャで隠蔽を頑張っていたが、メモリ効率や構文の面で限界があった。
- 現代の JavaScript では、`#` をつけるだけでランタイムレベルの堅牢なカプセル化が手に入る。
- 変数だけでなく、メソッドやgetter/setterにも `#` は使える。
- クラスの外や子クラスからも直接アクセスはできない(意図せぬ結合を防ぐ最高の仕様)。
ここをクリアできれば、あなたの書くコードは「動くだけのスクリプト」から「大規模開発にも耐えうる堅牢なアプリケーションコード」へと一段階レベルアップします。ぜひ、明日のコードから `#` をガンガン活用してみてくださいね。
それでは、また次の知見でお会いしましょう!バッチリマスターしていきましょう!