【実務・中級編】 プライベートクラスフィールドのスコープ – JavaScript実践ガイド

現場の最前線でコードを書いていると、「動けばいい」というフェーズから「なぜこう動くのか?」という本質的な問いにぶつかる瞬間が必ず来る。特にクラスのプライベートフィールド(`#`)は、単なる「外部から隠すための装飾」だと思って使うと、いざという時に痛い目を見る。

今日は、この「`#`」が単なる構文糖衣ではなく、JavaScriptのレキシカルスコープという深淵とどう結びついているのか、その「リアルな正体」を紐解いていこう。

—

1. #は単なる「隠し味」ではない

まず、多くのエンジニアが勘違いしている点から正そう。`#`で始まるプライベートプロパティは、単に「外からアクセスできないプロパティ」ではない。これらは、そのクラスが宣言された「レキシカルスコープ(静的スコープ)」に強固に紐付けられた、識別子そのものなんだ。

従来の `_privateProperty` のような慣習的な命名は、単なる「お作法」に過ぎなかった。しかし、`#` を使ったプライベートフィールドは、ブラウザ(V8エンジンなど)の内部実装レベルで、クラスのインスタンスではなく、「クラスのスコープ」に紐付いた識別子として管理される。

つまり、`#` を使ったフィールドは、クラスの外からは物理的に存在が観測できない。これは `Object.keys()` や `for…in` で列挙しようとしても無駄だ。ブラウザは「そのスコープ内にいない者には、存在すら教えない」という鉄壁のガードを敷いている。

2. 実務で遭遇する「スコープの境界線」

百聞は一見に如かず。まずは、この「スコープへの紐付き」を意識した実装を見てほしい。

class SecureVault {
// クラスのスコープにのみ存在するプライベートフィールド
#secretKey = “SUPER_SECRET_TOKEN”;

constructor(key) {
this.#secretKey = key;
}

// 内部メソッドはレキシカルスコープ内にあるため、#にアクセス可能
getDecryptedData() {
return this.#decrypt(this.#secretKey);
}

// プライベートメソッドも同様に、クラススコープ外からは一切見えない
#decrypt(key) {
return `Decrypted with ${key}`;
}
}

const vault = new SecureVault(“MY_KEY”);

// 以下の操作はすべてエラーになる(SyntaxErrorまたはTypeError)
// console.log(vault.#secretKey); // 構文レベルで弾かれる
// console.log(vault.#decrypt()); // 同上

このコードのポイントは、`#secretKey` が `vault` というインスタンスの中にあるように見えて、実際には `SecureVault` というクラスの定義スコープに深く埋め込まれている ということだ。

3. なぜ「巻き上げ(Hoisting)」が効かないのか

ここで一つ、JavaScriptの深淵に触れよう。`var` や `function` 宣言で見られる「巻き上げ」を期待してはいけない。プライベートフィールドは、クラスの宣言が評価されるまで存在しない。

これは、`let` や `const` が持つ「TDZ(一時的デッドゾーン)」に近い挙動だが、さらに厳格だ。クラス定義の外からアクセスしようとすると、実行時エラーではなく、「パースエラー(SyntaxError)」 を吐く。なぜなら、ブラウザはコードを解析する時点で「その識別子がそこにあるべきではない」と判断できるからだ。

4. 現場で役立つ「プライベートフィールド」の真価

実務でこの機能をどう活かすか? 私がチームに勧めているのは、「外部に公開するインターフェース(API)と、内部の状態管理を物理的に分離する」という設計だ。

例えば、Reactのクラスコンポーネントや、複雑な状態を持つサービスロジックを書くとき、`#` を使うと「何が外部公開用で、何が内部の秘密兵器なのか」がひと目で分かるようになる。

class UserSession {
#status = “offline”; // 外部から書き換えられたくない状態

// 外部には状態を読み取るためのメソッドだけを提供する
get isOnline() {
return this.#status === “online”;
}

login() {
// 複雑な認証ロジックをここに隠蔽
this.#performInternalAuth();
this.#status = “online”;
}

#performInternalAuth() {
console.log(“認証処理を実行中…”);
}
}

const user = new UserSession();
user.login();
console.log(user.isOnline); // true
// user.#status = “hacked”; // SyntaxError! 外部からの改竄は不可能

最後に:シニアからのアドバイス

`#` を使うべきか、それとも従来の命名規則(`_`)で済ませるべきか。これはよく議論になるが、私の答えは明確だ。

「外部から触られるとバグの温床になる、あるいは内部実装を隠蔽して将来的に破壊的変更を加えたい箇所」 には、迷わず `#` を使え。

JavaScriptという言語は、自由であるがゆえに「壊れやすい」。プライベートクラスフィールドは、その自由の中に「規律」を強制的に作り出す、強力な武器だ。このスコープの境界線を正しく理解し、堅牢なアーキテクチャを築き上げてくれ。

何か不明点があれば、またいつでも相談してくれ。コードの裏側まで見通せるようになれば、君の書くコードは一段階上のクオリティに到達するはずだ。

コメント

タイトルとURLをコピーしました