protectedの役割とJavaScriptで使用できない理由をわかりやすく解説

スポンサーリンク
スポンサーリンク
amazon
スマイルSALE
--:--:--
ad. 価格範囲を指定して商品を探せます

protectedの概要

アクセス制御(可視性) JavaScript将来の予約語

protected

概要 protected(プロテクテッド)は、オブジェクト指向プログラミング(OOP)において、クラスのメンバー(プロパティやメソッド)の可視性を制限する「アクセス修飾子」の一つです。
「定義したクラス自身」および「そのクラスを継承したサブクラス(子クラス)」からのアクセスのみを許可し、それ以外の外部(インスタンスの直接参照など)からのアクセスを遮断する役割を持っています。しかし、現行のJavaScript(ECMAScript)仕様および実行エンジンにおいては実装されておらず、言語機能としてはサポートされていません。将来のための予約語として登録されているため、実コード内に修飾子として直書きすると構文エラー(SyntaxError)となります。

わかりやすく説明 protectedは、クラスのデータに対して「身内(自分と、自分を継承した子供たち)のメンバー間だけで共有し、他人のアクセスは拒絶する」という境界線を引くためのキーワードです。JavaScript単体では使えませんが、TypeScriptなどで強力にサポートされています。

  • カプセル化(内部構造の隠蔽)の概念において、完全非公開の private と、完全公開の public の中間層に位置する保護レベルを提供します。
  • JavaScript標準では # シンボルを用いた private フィールドは利用可能ですが、子クラスへの継承・共有を前提とした protected 相当の純正構文は存在しません。
  • バニラJavaScriptでこの制限を再現する場合、暗黙のルール(命名規則)をチーム内で共有するか、仕様上のスコープを応用したカプセル化設計が必要となります。

1. 他言語におけるprotectedの本来の役割(アクセス制御の3大モデル)

静的型付け言語(Java、C#、C++など)におけるアクセス修飾子は、オブジェクトの整合性を保つための防壁です。protected は「継承関係にあるクラス間でのみ再利用したい共通パーツ」を安全に定義するために欠かせない機能です。

// Java等におけるprotectedによるカプセル化の例
class Parent {
    // 親クラスと子クラス内からのみ触れる変数を定義
    protected String message = "こんにちは!";
}

class Child extends Parent {
    public void showMessage() {
        // 親から継承したprotectedメンバーなので、子クラス内からは自由にアクセス可能
        System.out.println(message); 
    }
}

class Main {
    public static void main(String[] args) {
        Child child = new Child();
        // 🛑 外部(非継承クラス)からの直接参照は、コンパイルの段階で拒絶(エラー)されます
        // System.out.println(child.message); 
    }
}

2. JavaScriptにおけるprotectedの代替実装アプローチ

JavaScriptには protected 構文がないため、開発現場ではコードの書き方や別の言語仕様を組み合わせることで、実質的なカプセル化を模倣しています。

アプローチA: 慣習的な命名規則(アンダースコア prefix)による暗示

最も古典的かつ広く普及している手法は、プロパティやメソッドの先頭にアンダースコア(_)を付与するパターンです。これは技術的なアクセス制限ではありませんが、「これは身内向けのプロパティなので外部から直接書き換えないでください」という開発者間の強いシグナル(暗黙の了解)として機能します。

// アンダースコアを用いた擬似的な保護設計
class Parent {
    constructor() {
        // _(アンダースコア)付きのプロパティは「触るな危険」の合図
        this._protectedValue = "保護されたデータ";
    }
}

class Child extends Parent {
    access() {
        // 子クラス内での利用は、設計の意図通りなので問題ありません
        return this._protectedValue; 
    }
}

const child = new Child();
console.log(child.access()); // 出力: 保護されたデータ

// ⚠️ 注意:言語機能としては「パブリック」なため、外部からアクセスできてしまいます
console.log(child._protectedValue); // アクセスできてしまう(開発者のモラルに依存)

アプローチB: ES2020の「privateフィールド(#)」を起点とした間接参照

JavaScriptの標準仕様であるプライベート修飾子(#)は、子クラスへの継承すら拒絶する完全な隠蔽(private)を行います。これを応用し、親クラスに「プライベート変数を子クラスへ受け渡すための仲介メソッド」を用意することで、堅牢な protected 構造をエミュレートできます。

// プライベートフィールドを利用した安全なデータ受け渡し
class Parent {
    // クラスの外からは完全に不可視となる内部変数
    #internalData = "厳重に保護されたデータ";

    // 子クラスに利用を許可するためのパブリックなゲッターメソッドを定義
    _getSharedData() {
        return this.#internalData;
    }
}

class Child extends Parent {
    show() {
        // 親クラスのメソッドを経由することで、間接的に親の秘匿データにアクセス可能
        return this._getSharedData(); 
    }
}

const child = new Child();
console.log(child.show()); // 出力: 厳重に保護されたデータ
// console.log(child.#internalData); // 🛑 構文エラー(外部からは完全に遮断)

3. TypeScriptにおける本来のprotectedの活用

フロントエンドやNode.jsの開発において、他言語と同等の厳格な protected によるコンパイルチェックを行いたい場合は、TypeScriptを利用するのが現在最も確実な解法です。

// TypeScriptのアクセス修飾子による静的防御
class Account {
    // 自身のクラスと、派生した子クラスにのみ開示
    protected baseRate = 1.05;
}

class PremiumAccount extends Account {
    public calculateBonus(amount: number) {
        // 子クラス内から親の protected フィールドを参照して計算
        return amount * this.baseRate * 1.2; 
    }
}

const acc = new PremiumAccount();
// ⚠️ エラー:Property 'baseRate' is protected and only accessible within class 'Account' and its subclasses.
// console.log(acc.baseRate); 
  • TypeScriptの protected 修飾子も、JavaScriptへコンパイルされると最終的には消滅して通常のプロパティに変換されますが、エディタ上やビルド時に不正アクセスを完全にシャットアウトできるため、大規模開発における安全性が飛躍的に向上します。

注意点

  • JavaScriptソースコードへの直書きによるクラッシュ: protected はECMAScript仕様で「将来の予約語」に指定されているため、トランスパイラ(TypeScriptなど)を通さないピュアなJavaScript環境(.jsファイル)で直接記述すると、ランタイムが構文エラーを発生させます。
    // 🛑 純粋なJavaScriptでのNGコード
    class User {
        protected name = "unknown"; // ❌ SyntaxError: Unexpected token 'name'
    }
    
  • アンダースコア(_)の防御力の低さ: アンダースコアを付ける手法は広く使われていますが、実体はただのパブリックプロパティです。サードパーティ製のライブラリを組み込む際や、チーム開発においてルールが形骸化した場合に、外部から意図しない破壊的な書き換えが行われるリスクを内包しています。
    // ⚠️ 暗黙のルールが破られる例
    const p = new Parent();
    p._protectedValue = "不正に書き換えられたデータ"; // 言語機能としてブロックできない
    

よくある質問

Q: private#)がJavaScriptに導入されたのであれば、将来的に protected というキーワードも公式サポートされる予定はありますか?
A: 現時点において、JavaScriptコアに protected キーワードを導入する具体的なプロポーザル(仕様提案)の標準化が進む兆しはありません。
JavaScriptの委員会(TC39)は、動的な言語としてのシンプルさを重視しており、すでに導入された #(プライベートフィールド)によってオブジェクトのカプセル化の要求は最低限満たせると判断しています。protected のような複雑なアクセス階層チェックは、実行時の処理負荷を高める要因にもなるため、コンパイルレイヤー(TypeScript)側にその役割を委ねるという役割分担が定着しています。
Q: protected を模倣する目的で、親の #private フィールドを子クラスでオーバーライド(上書き再定義)することはできますか?
A: できません。JavaScriptのプライベートフィールド(#)は、そのプロパティが宣言された「クラスのスコープ内」に完全に縛られます。
子クラスで同じ #variable という名前のプロパティを定義したとしても、それは親クラスの変数とはメモリ領域が全く異なる「名前が同じだけの別物(独立したプライベート変数)」として扱われます。親のデータを変更・上書きしたい場合は、親クラス側にアンダースコア付きのセッターメソッドを明示的に用意する必要があります。

// 親のprivateデータを子クラスから更新可能にする設計例
class Asset {
    #value = 100;
    
    // 擬似的なprotectedセッター関数
    _setValue(newValue) {
        this.#value = newValue;
    }
}
class SpecialAsset extends Asset {
    update() {
        this._setValue(200); // ⭕ 親クラスの仲介関数を介して安全に変更
    }
}

まとめ

protected修飾子は、クラスの内部データを「継承」という血縁関係の内部だけで排他的に共有するためのオブジェクト指向的なアクセス制御機構です。

  • ネイティブのJavaScript環境においては未実装の予約語であるため、直接記述するとシンタックスエラーになる点に留意してください。
  • ピュアなJavaScriptで設計する場合は、認知負荷の低い「アンダースコア(_)による名前空間の分離」か、堅牢な「プライベートフィールド(#)とアクセサー関数の併用」によって構造を表現します。
  • クラスの階層構造が複雑で、厳密なスコープチェックによるバグの未然防止(型安全性)が求められるエンタープライズ向けのシステム開発では、TypeScriptによる protected の活用が強力な解決策となります。