JavaScript | ES6+ 文法:クラス構文 – プライベートフィールド(#)

JavaScript JavaScript
スポンサーリンク

プライベートフィールド(#)とは何か

プライベートフィールドは、class の中で
「クラスの外から絶対に直接触られたくない値」を隠すための仕組みです。

書き方は、名前の前に # をつけます。

class User {
  #name; // プライベートフィールド

  constructor(name) {
    this.#name = name;
  }

  greet() {
    console.log(`こんにちは、${this.#name} です`);
  }
}

const u = new User("Alice");
u.greet();       // こんにちは、Alice です

console.log(u.#name); // エラー:クラスの外からはアクセスできない
JavaScript

ここが重要です。
#nameクラスの外からも、継承した子クラスからも、インスタンス経由でも「直接」は触れない 完全プライベートなフィールドです。
「class の中だけの秘密」と思ってください。

どう書くのか(基本構文とルール)

宣言と代入の基本パターン

プライベートフィールドを使うときは、クラスの一番上あたりで宣言します。

class Counter {
  #value; // 宣言

  constructor(initial = 0) {
    this.#value = initial; // 代入
  }

  increment() {
    this.#value++;
  }

  getValue() {
    return this.#value;
  }
}

const c = new Counter(10);
c.increment();
console.log(c.getValue()); // 11
JavaScript

ポイントは次の2つです。

  1. クラスの中で #名前 を宣言する
  2. そのクラスの中でだけ this.#名前 で読み書きできる

クラスの外で c.#value と書くと、即エラーになります。

プロパティ名は「#付き」と「#なし」で別物

#namename は、完全に別のプロパティです。

class User {
  #name;

  constructor(name) {
    this.#name = name;
    this.name = name + "(公開用)";
  }

  show() {
    console.log("private:", this.#name);
    console.log("public :", this.name);
  }
}

const u = new User("Alice");
u.show();
// private: Alice
// public : Alice(公開用)

console.log(u.name);   // OK(公開プロパティ)
console.log(u.#name);  // エラー(プライベート)
JavaScript

ここが重要です。
#namename は、全く別のフィールド
# が付くと「完全なプライベート」、付かないものは普通のプロパティ(外から見える)です。

なぜプライベートフィールドが必要なのか(カプセル化の本質)

「触られたら困る中身」を守る

例えば銀行口座クラスを考えます。

class BankAccount {
  #balance; // 外から直接いじってほしくない

  constructor(initialBalance = 0) {
    this.#balance = initialBalance;
  }

  deposit(amount) {
    if (amount <= 0) throw new Error("金額がおかしい");
    this.#balance += amount;
  }

  withdraw(amount) {
    if (amount <= 0) throw new Error("金額がおかしい");
    if (this.#balance < amount) throw new Error("残高不足");
    this.#balance -= amount;
  }

  getBalance() {
    return this.#balance;
  }
}

const account = new BankAccount(1000);
account.deposit(500);
// account.#balance = 999999; // こういうズルを完全に防げる

console.log(account.getBalance()); // 1500
JavaScript

もし # をつけないと、外からこう書けてしまいます。

account.balance = 999999; // 本当はやられたくない

ここが重要です。
プライベートフィールドは、「外から勝手にいじられると困る内部状態」を守る壁です。
クラスの外からは、ちゃんと用意されたメソッド(deposit, withdraw など)を通してしか変更できなくなります。

「外から見せたい情報」と「隠したい内部」を分ける

設計としては、よくこうします。

  • プライベートフィールド:内部の生データ(#balance, #password など)
  • パブリックメソッド:そのデータを安全に扱うための機能(deposit, checkPassword など)
class User {
  #passwordHash;

  constructor(name, password) {
    this.name = name;
    this.#passwordHash = this.#hash(password);
  }

  #hash(text) {                 // プライベートメソッド
    return `hashed:${text}`;
  }

  checkPassword(password) {
    return this.#passwordHash === this.#hash(password);
  }
}

const u = new User("Alice", "secret");

console.log(u.name);                  // 公開OK
console.log(u.checkPassword("xxx"));  // false
console.log(u.checkPassword("secret"));// true

// console.log(u.#passwordHash);      // エラー
// u.#hash("secret");                 // エラー
JavaScript

ここが重要です。
クラスの中だけで完結させたい“裏側のロジック”は全部 # 付きにする
これがカプセル化(中身の隠蔽)の実践です。

プライベートメソッドも書ける

#付きでメソッドも隠せる

フィールドだけでなく、メソッドにも # をつけられます。

class Logger {
  #prefix;

  constructor(prefix = "LOG") {
    this.#prefix = prefix;
  }

  #format(message) { // プライベートメソッド
    const time = new Date().toISOString();
    return `[${this.#prefix}] ${time} - ${message}`;
  }

  log(message) {     // 公開メソッド
    console.log(this.#format(message));
  }
}

const logger = new Logger("APP");
logger.log("起動しました");

// logger.#format("直接呼びたい"); // エラー:外から呼べない
JavaScript

ここが重要です。
「外から直接呼ばせたくない補助的な処理」は、#付きメソッドにしてしまう と、
クラスの「公開API」と「内部実装」が綺麗に分かれます。

継承(extends)とプライベートフィールド

子クラスからも直接触れない

プライベートフィールドは「そのクラス専用」です。
継承した子クラスからも直接は触れません。

class Animal {
  #name;

  constructor(name) {
    this.#name = name;
  }

  getName() {
    return this.#name;
  }
}

class Dog extends Animal {
  bark() {
    // console.log(this.#name); // エラー:親の #name は見えない
    console.log(this.getName() + "「ワン!」");
  }
}

const d = new Dog("ポチ");
d.bark(); // ポチ「ワン!」
JavaScript

ここが重要です。
プライベートフィールドは “クラス単位” の秘密であって、“継承階層全体” の秘密ではない
子クラスに見せたい場合は、親クラス側で protected 的な役割のメソッド(getName() など)を用意してあげます。

旧来の「擬似プライベート」との違い

「慣習」ではなく「言語仕様としての隠蔽」

昔は、「プライベートっぽく見せる」ためにこんな書き方をすることがありました。

class OldStyle {
  constructor() {
    this._value = 0; // 先頭に _ をつけて「触らないでね」という合図
  }
}
JavaScript

しかしこれは「約束」にすぎず、普通に外から触れてしまいます。

const o = new OldStyle();
o._value = 999; // 触れてしまう
JavaScript

#value は、「仕様として絶対に触れない」ので、
「約束」ではなく「本物のプライベート」 になっています。

初心者向けにざっくり言うと:

  • _value → 「本当は触らないでほしいけど、技術的には触れてしまう」
  • #value → 「そもそも構文エラーになるので触れない」

という違いです。

例題でプライベートフィールドに慣れる

例1:安全なカウンター

class Counter {
  #value;

  constructor(initial = 0) {
    this.#value = initial;
  }

  increment() {
    this.#value++;
  }

  decrement() {
    this.#value--;
  }

  get value() {       // getter で外に出す
    return this.#value;
  }
}

const c = new Counter(10);
c.increment();
c.decrement();
console.log(c.value);   // 10

// c.#value = 999;      // エラー:外から改ざんできない
JavaScript

例2:パスワード付きユーザー

class SecureUser {
  #passwordHash;

  constructor(name, password) {
    this.name = name;
    this.#passwordHash = this.#hash(password);
  }

  #hash(text) {
    // 実際はもっと複雑なハッシュ関数を使う
    return `hash:${text}`;
  }

  checkPassword(password) {
    return this.#passwordHash === this.#hash(password);
  }
}

const u = new SecureUser("Alice", "secret");

console.log(u.checkPassword("secret")); // true
console.log(u.checkPassword("xxx"));    // false

// console.log(u.#passwordHash);        // エラー
JavaScript

例3:内部状態を隠した設定クラス

class Config {
  #options;

  constructor(options = {}) {
    this.#options = {
      theme: "light",
      lang: "ja",
      ...options
    };
  }

  get(key) {
    return this.#options[key];
  }

  set(key, value) {
    this.#options[key] = value;
  }

  dump() {
    return { ...this.#options }; // コピーを返す
  }
}

const cfg = new Config({ theme: "dark" });
console.log(cfg.get("theme")); // dark

cfg.set("lang", "en");
console.log(cfg.dump());       // { theme: 'dark', lang: 'en' }

// cfg.#options = {};          // エラー:内部構造は隠蔽
JavaScript

まとめ

プライベートフィールド(#)の核心は、
「クラスの中だけで使える、本物のプライベートなプロパティやメソッドを持てるようにする」 ことです。

押さえておきたいポイントは:

  • #name のように宣言し、クラスの中で this.#name として使う
  • クラスの外からも、子クラスからも、obj.#name は構文エラーになる
  • 「外から触られると困るデータ・ロジック」を # 付きにして、メソッド経由でだけ操作させる
  • プライベートメソッド(#method() {})も同じように定義できる
  • 旧来の _name のような「慣習」ではなく、仕様として守られる本物のカプセル化

まずは、自分で作ったクラスの中で「絶対に外から触られてほしくない値」を1つ決めて、
それを # 付きフィールドにしてみてください。
クラスの「外に見せる顔」と「内側」の境界線が、ぐっとはっきりして見えてくるはずです。

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