TypeScriptのプライベートキーワードとプライベートフィールドの違いは何ですか?


27

TypeScript 3.8以降では、privateキーワードを使用してメンバーをプライベートとしてマークすることの違いは何ですか?

class PrivateKeywordClass {
    private value = 1;
}

そしてJavaScriptのために提案され#プライベートフィールドを使用します

class PrivateFieldClass {
    #value = 1;
}

どちらを優先するべきですか?


回答:


43

プライベートキーワード

TypeScript のプライベートキーワードは、コンパイル時の注釈です。これは、プロパティがそのクラス内でのみアクセス可能であることをコンパイラーに伝えます。

class PrivateKeywordClass {
    private value = 1;
}

const obj = new PrivateKeywordClass();
obj.value // compiler error: Property 'value' is private and only accessible within class 'PrivateKeywordClass'.

ただし、たとえば型情報をキャストすることで、コンパイル時のチェックを簡単に回避できます。

const obj = new PrivateKeywordClass();
(obj as any).value // no compile error

privateキーワードはまた、実行時に強制されていません

放出されたJavaScript

TypeScriptをJavaScriptにコンパイルするとき、privateキーワードは単に削除されます。

class PrivateKeywordClass {
    private value = 1;
}

になる:

class PrivateKeywordClass {
    constructor() {
        this.value = 1;
    }
}

これから、privateキーワードがランタイム保護を提供しない理由がわかります。生成されたJavaScriptでは、これは通常のJavaScriptプロパティにすぎません。

プライベートフィールド

プライベートフィールドにより、実行時にプロパティがプライベート保たれます

class PrivateFieldClass {
    #value = 1;

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

const obj = new PrivateFieldClass();

// You can't access '#value' outside of class like this
obj.value === undefined // This is not the field you are looking for.
obj.getValue() === 1 // But the class itself can access the private field!

// Meanwhile, using a private field outside a class is a runtime syntax error:
obj.#value

// While trying to access the private fields of another class is 
// a runtime type error:
class Other {
    #value;

    getValue(obj) {
        return obj.#value // TypeError: Read of private field #value from an object which did not contain the field
    }
}

new Other().getValue(new PrivateKeywordClass());

TypeScriptは、クラス外のプライベートフィールドを使用しようとすると、コンパイル時エラーも出力します。

プライベートフィールドへのアクセスエラー

プライベートフィールドはJavaScriptプロポーザルから取得され、通常のJavaScriptでも機能します。

放出されたJavaScript

TypeScriptでプライベートフィールドを使用し、es6or などの古いバージョンのJavaScriptをターゲットにしている場合es2018、TypeScriptはプライベートフィールドのランタイム動作をエミュレートするコードを生成しようとします

class PrivateFieldClass {
    constructor() {
        _x.set(this, 1);
    }
}
_x = new WeakMap();

をターゲットesnextにしている場合、TypeScriptはプライベートフィールドを発行します。

class PrivateFieldClass {
    constructor() {
        this.#x = 1;
    }
    #x;
}

どちらを使用すればよいですか?

それはあなたが達成しようとしていることに依存します。

privateキーワードは、罰金のデフォルトです。それは、それが達成するように設計されたものを達成し、TypeScript開発者によって長年首尾よく使用されてきました。また、既存のコードベースがある場合、プライベートフィールドを使用するようにすべてのコードを切り替える必要はありません。これはesnext、TSがプライベートフィールドに対して発行するJSがパフォーマンスに影響を与える可能性があるため、ターゲティングしていない場合に特に当てはまります。また、プライベートフィールドには、privateキーワードとは微妙ですが重要な違いがあります。

ただし、ランタイムプライベートを適用する必要がある場合やesnextJavaScript を出力する場合は、プライベートフィールドを使用する必要があります。

また、JavaScript / TypeScriptエコシステム内でプライベートフィールドが広く普及するにつれて、どちらか一方を使用する際の組織/コミュニティの規約も進化することにも注意してください。

その他の注意点の違い

  • Object.getOwnPropertyNamesと同様のメソッドによってプライベートフィールドが返されない

  • プライベートフィールドはシリアル化されません JSON.stringify

  • 継承に関する重要なエッジケースがあります。

    たとえばTypeScriptは、スーパークラスのプライベートプロパティと同じ名前のサブクラスのプライベートプロパティを宣言することを禁止します。

    class Base {
        private value = 1;
    }
    
    class Sub extends Base {
        private value = 2; // Compile error:
    }

    これはプライベートフィールドには当てはまりません。

    class Base {
        #value = 1;
    }
    
    class Sub extends Base {
        #value = 2; // Not an error
    }
  • private初期化子のないキーワード私有財産は、放出されたJavaScriptでプロパティ宣言を生成しません。

    class PrivateKeywordClass {
        private value?: string;
        getValue() { return this.value; }
    }

    コンパイルして:

    class PrivateKeywordClass {
        getValue() { return this.value; }
    }

    一方、プライベートフィールドは常にプロパティ宣言を生成します。

    class PrivateKeywordClass {
        #value?: string;
        getValue() { return this.#value; }
    }

    コンパイルする(ターゲット設定時esnext):

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

参考文献:


4

使用例:#-privateフィールド

序文:

コンパイル時および実行時のプライバシー

#-privateフィールドは、コンパイル時および実行時のプライバシーを提供しますが、これは「ハッキング可能」ではありません。これは、クラス本体の外部からメンバーに直接アクセスできないようにするメカニズムです。

class A {
    #a: number;
    constructor(a: number) {
        this.#a = a;
    }
}

let foo: A = new A(42);
foo.#a; // error, not allowed outside class bodies
(foo as any).#bar; // still nope.

安全なクラス継承

#-privateフィールドは一意のスコープを取得します。クラス階層は、同じ名前のプライベートプロパティを誤って上書きすることなく実装できます。

class A { 
    #a = "a";
    fnA() { return this.#a; }
}

class B extends A {
    #a = "b"; 
    fnB() { return this.#a; }
}

const b = new B();
b.fnA(); // returns "a" ; unique property #a in A is still retained
b.fnB(); // returns "b"

privateプロパティが上書きされる危険がある場合、TSコンパイラは幸いにもエラーを発行します(この例を参照)。しかし、コンパイル時機能の性質上、コンパイルエラーが無視されたり、放出されたJSコードが利用されたりすると、実行時にすべてが可能になります。

外部ライブラリ

ライブラリの作成者は#、クライアントに重大な変更を加えることなく、プライベート識別子をリファクタリングできます。反対側のライブラリユーザーは、内部フィールドへのアクセスから保護されています。

JS APIは#-privateフィールドを省略します

組み込みのJS関数およびメソッドは、#-privateフィールドを無視します。これにより、実行時にプロパティの選択がより予測可能になります。例:Object.keysObject.entriesJSON.stringifyfor..inループなど(コードサンプル、またマットBiernerの参照答えを):

class Foo {
    #bar = 42;
    baz = "huhu";
}

Object.keys(new Foo()); // [ "baz" ]

使用例:privateキーワード

序文:

内部クラスAPIおよび状態へのアクセス(コンパイル時のみのプライバシー)

privateクラスのメンバーは、実行時の従来のプロパティです。この柔軟性を利用して、クラスの内部APIまたは状態に外部からアクセスできます。コンパイラチェック、タイプアサーション、動的プロパティアクセスなどのメカニズムを満足させるために、@ts-ignoreとりわけ使用できます。

型アサーション(as/ <>)とany型付き変数の割り当ての例:

class A { 
    constructor(private a: number) { }
}

const a = new A(10);
a.a; // TS compile error
(a as any).a; // works
const casted: any = a; casted.a // works

TS privateは、escape-hatchを使用してメンバーの動的プロパティアクセスも許可します。

class C {
  private foo = 10;
}

const res = new C()["foo"]; // 10, res has type number

プライベートアクセスはどこで意味がありますか?(1)単体テスト、(2)デバッグ/ログ記録の状況、または(3)プロジェクト内部クラス(オープンエンドリスト)を使用したその他の高度なケースシナリオ。

内部変数へのアクセスは少し矛盾しています-そうでなければprivate、最初にそれらを作成しなかっただろう 。例をあげると、単体テストは、実装の詳細として非表示のプライベートフィールドを持つブラック/グレーのボックスであると想定されています。ただし、実際には、ケースごとに有効なアプローチがある場合があります。

すべてのES環境で利用可能

TS private修飾子は、すべてのESターゲットで使用できます。#-privateフィールドはtarget ES2015/ ES6以上でのみ使用できます。ES6 +では、WeakMap内部的に下位レベルの実装として使用されます(こちらを参照)。#現在、ネイティブプライベートフィールドにはが必要ですtarget esnext

一貫性と互換性

チームは、コーディングガイドラインとリンタールールを使用しprivateて、を唯一のアクセス修飾子として使用することを強制できます。この制限は、一貫性を#維持し、下位互換性のある方法で-privateフィールド表記との混乱を回避するのに役立ちます。

必要に応じて、パラメータープロパティ(コンストラクターの割り当ての省略形)がショーストッパーになります。これらはprivateキーワードでのみ使用でき、-privateフィールドに実装する計画はまだありません#

その他の理由

  • private一部のダウンレベリングのケースでは、実行時のパフォーマンスが向上する可能性があります(ここを参照)。
  • これまでTSで使用できるハードプライベートクラスメソッドはありません。
  • 一部の人々は、privateキーワード表記をよりよく好みます😊。

両方に注意

どちらのアプローチでも、コンパイル時にある種の名義型またはブランド型が作成されます。

class A1 { private a = 0; }
class A2 { private a = 42; }

const a: A1 = new A2(); 
// error: "separate declarations of a private property 'a'"
// same with hard private fields

また、どちらもインスタンス間のアクセスを許可します。クラスAAインスタンスは他のインスタンスのプライベートメンバーにアクセスできます。

class A {
    private a = 0;
    method(arg: A) {
        console.log(arg.a); // works
    }
}

出典

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.