アクセスできないはずのTypeScriptプライベートメンバーにアクセスできるのはなぜですか?


108

TypeScriptでのプライベートメンバーの実装を調べていますが、少し混乱しています。Intellisenseはプライベートメンバーへのアクセスを許可していませんが、純粋なJavaScriptではそれだけです。これにより、TSはプライベートメンバーを正しく実装しないと思います。何かご意見は?

class Test{
  private member: any = "private member";
}
alert(new Test().member);

IntelliSenseがalert()の行のプライベートメンバーを提供しないのはなぜですか?
エスレンジ

7
いいえ。それがインテリセンスのための砂糖であり、コンパイルするJavaScriptのためではないのに、なぜTSにプライベートがあるのか​​と思います。typescriptlang.org/Playgroundで実行されるこのコードは、プライベートメンバーの値を警告します。
Sean Feldman

前述のように、アイテムをプライベートにするには、プライベートコンテキストで変数として宣言する必要があります。typescriptはプロトタイプに追加するよりも非効率になる可能性があるため、これを行わないと思います。また、型の定義も変更されます(プライベートメンバーは実際にはクラスの一部ではありません)
Shane

プロトタイプに存在する実際のプライベート変数が必要な場合は、オーバーヘッドがかかりますが、GitHubでそれを行うClassJSと呼ばれるライブラリを作成しました:github.com/KthProg/ClassJS
KthProg 2017

回答:


97

型チェックと同様に、メンバーのプライバシーはコンパイラー内でのみ適用されます。

プライベートプロパティは通常のプロパティとして実装され、クラス外のコードはそれにアクセスできません。

クラス内で何かを本当にプライベートにするには、それをクラスのメンバーにすることはできません。オブジェクトを作成するコード内の関数スコープ内に作成されるローカル変数になります。つまり、クラスのメンバーのように、つまりthisキーワードを使用してアクセスすることはできません。


25
ただし、JavaScriptプログラマーがローカル変数をオブジェクトコンストラクターに入れてプライベートフィールドとして使用するのは珍しいことではありません。彼らがこのようなものをサポートしていなかったのには驚いています。
エリック、

2
@Eric:TypeScriptは、コンストラクター内でプロトタイプとしてメソッドを追加するのではなく、メソッドのプロトタイプを使用するため、コンストラクター内のローカル変数にはメソッドから到達できません。クラスの関数ラッパー内にローカル変数を作成することは可能かもしれませんが、それを行う方法はまだ見つかりません。ただし、これはローカル変数であり、プライベートメンバーではありません。
Guffa

40
これは私がフィードバックを提供しているものです。私はそれが公開モジュールパターンを作成するオプションを提供するはずであると私は信じています。そうすればプライベートメンバーはプライベートのままで、パブリックメンバーはJavaScriptでアクセスできます。これは一般的なパターンであり、TSとJSで同じアクセシビリティを提供します。
John Papa

プライベート静的メンバーに使用できるソリューションがあります:basarat.com/2013/03/real-private-static-class-members-in.html
basarat

1
@BasaratAli:これはクラスのメソッド内で使用できる静的変数ですが、クラスのメンバーではないため、thisキーワードを使用してアクセスすることはできません。
Guffa

37

JavaScriptはプライベート変数をサポートします。

function MyClass() {
    var myPrivateVar = 3;

    this.doSomething = function() {
        return myPrivateVar++;        
    }
}

TypeScriptでは、これは次のように表現されます。

class MyClass {

    doSomething: () => number;

    constructor() {
        var myPrivateVar = 3;

        this.doSomething = function () {
            return myPrivateVar++;
        }
    }
}

編集

このアプローチは、絶対に必要な場合にのみ、一時的に使用する必要があります。たとえば、パスワードを一時的にキャッシュする必要がある場合です。

このパターン(JavascriptまたはTypescriptとは無関係)を使用するとパフォーマンスが低下するため、絶対に必要な場合にのみ使用してください。


typescriptはvar _this、スコープ関数で使用するように設定することで、常にこれを行いませんか?なぜクラススコープでそれをしているのでしょうか?
DrSammyD 2016年

いいえ。var_thisはこれへの参照にすぎません。
マーティン

2
プライベート変数ではなく、コンストラクタ変数と呼ぶ方が正確です。これらはプロトタイプメソッドでは表示されません。
ローマンM.コス2016

1
そうそう、問題は代わりに他の問題でした。申し訳ありませんが、作成したすべてのインスタンスに対して、doSomethingが再度作成されます。これは、プロトタイプチェーンの一部ではないためです。
Barbu Barbu 2018年

1
@BarbuBarbuはい、同意します。これはこのアプローチの大きな問題であり、回避する必要がある理由の1つです。
マーティン

11

WeakMapのサポートがより広く利用できるようになると、ここの例3 詳しく説明する興味深い手法があります

インスタンスメソッドだけでなくプロトタイプメソッドからデータにアクセスできるようにすることで、プライベートデータを許可し、Jason Evansサンプルのパフォーマンスコストを回避します。

リンクされたMDN WeakMapページには、Chrome 36、Firefox 6.0、IE 11、Opera 23、およびSafari 7.1でのブラウザーサポートがリストされています。

let _counter = new WeakMap();
let _action = new WeakMap();
class Countdown {
  constructor(counter, action) {
    _counter.set(this, counter);
    _action.set(this, action);
  }
  decrement() {
    let counter = _counter.get(this);
    if (counter < 1) return;
    counter--;
    _counter.set(this, counter);
    if (counter === 0) {
      _action.get(this)();
    }
  }
}

私はそれが好き!基本的に、プライベートプロパティを集約クラスに隠すことを意味します。最も楽しいのは... protectedパラメータのサポートを追加してみませんか?:D
ローマンM.コス2016

2
@RamtinSoltaniリンクされた記事によると、weakmapがどのように機能するかにより、ガベージコレクションが妨げられることはありません。この手法を使用しているときに誰かがさらに安全になりたい場合は、各ウィークマップからクラスインスタンスキーを削除する独自の廃棄コードを実装できます。
ライアントーマス

1
MDNページから:developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…。対照的に、ネイティブのWeakMapは、キーオブジェクトへの「弱い」参照を保持します。つまり、キーオブジェクトへの参照が他にない場合でも、ガベージコレクションを妨げません。これにより、マップ内の値のガベージコレクションの防止も回避されます。
ライアントーマス

@RyanThomas確かに、それは私が少し前に残した古いコメントでした。マップとは対照的に、WeakMapはメモリリークを引き起こしません。したがって、この手法を使用しても安全です。
Ramtin Soltani

@RamtinSoltaniでは、古いコメントを削除しますか?>
ErikE

10

TypeScript 3.8がリリースされると、アクセスできない、または含まれているクラスの外部で検出できないプライベートフィールドを宣言できるように なります

class Person {
    #name: string

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

    greet() {
        console.log(`Hello, my name is ${this.#name}!`);
    }
}

let jeremy = new Person("Jeremy Bearimy");

jeremy.#name
//     ~~~~~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.

プライベートフィールドは#文字で始まります

これらのプライベートフィールドは、privateキーワードでマークされたフィールドとは異なることに注意してください

参照 https://devblogs.microsoft.com/typescript/announcing-typescript-3-8-beta/


4

この問題に関する公式ディスカッションへのリンクを提供してくれたSean Feldmanに感謝します。リンクについては彼の回答を参照してください。

私は彼がリンクした議論を読みました、そしてここに重要なポイントの要約があります:

  • 提案:コンストラクターのプライベートプロパティ
    • 問題:プロトタイプ関数からアクセスできない
  • 提案:コンストラクターのプライベートメソッド
    • 問題:プロパティの場合と同じですが、プロトタイプのクラスごとに1回関数を作成することによるパフォーマンス上の利点が失われます。代わりに、インスタンスごとに関数のコピーを作成します
  • 提案:ボイラープレートを追加してプロパティアクセスを抽象化し、可視性を適用する
    • 問題:主なパフォーマンスのオーバーヘッド。TypeScriptは大規模アプリケーション向けに設計されています
  • 提案: TypeScriptは、コンストラクターとプロトタイプメソッドの定義をクロージャーで既にラップしています。プライベートメソッドとプロパティをそこに置く
    • そのクロージャにプライベートプロパティを配置する際の問題:それらは静的変数になります。インスタンスごとに1つはありません
    • そのクロージャにプライベートメソッドを配置する際の問題:thisある種の回避策がないとアクセスできません
  • 提案:プライベート変数名を自動的にマングルします
    • 反対の引数:これは命名規則であり、言語構造ではありません。自分で壊す
  • 提案:プライベートメソッドに@private注釈を付けると、その注釈を認識するミニファイアでメソッド名を効果的に縮小できます
    • これに対する重要な反論はない

放出されたコードに可視性サポートを追加することに対する全体的な反論:

  • 問題は、JavaScript自体に可視性修飾子がないことです。これはTypeScriptの問題ではありません。
  • JavaScriptコミュニティにはすでに確立されたパターンがあります。プライベートプロパティとメソッドの前にアンダースコアを付けてください。
  • TypeScriptデザイナーが真にプライベートなプロパティとメソッドは「不可能」ではないと言ったとき、それらは「設計上の制約の下では不可能」を意味しました。
    • 放出されたJSは慣用的です
    • ボイラープレートは最小限です
    • 通常のJS OOPと比較して追加のオーバーヘッドはありません

この回答がこの会話からのものである場合:typescript.codeplex.com/discussions/397651-、リンクを提供してください:D
Roman M. Koss

1
はい、それは会話ですが、私はショーンフェルドマンのこの質問への回答にリンクしました。彼がリンクを見つける仕事をしたので、私は彼に信用を与えたかったです。
アレクサンダーバード

2

TypeScriptでは、プライベート関数はクラス内でのみアクセスできます。お気に入り

ここに画像の説明を入力してください

プライベートメンバーにアクセスしようとすると、エラーが表示されます。次に例を示します。

ここに画像の説明を入力してください

注:javascriptでは問題なく、両方の機能に外部からアクセスできます。


4
OP:「しかし、純粋なJavaScriptにはすべてが揃っている」-生成されたJavaScriptが「プライベート」関数を公開しているという問題に対処していないと思います
alexanderbird

1
@alexanderbird私は彼がTypeScriptで通常十分であると言いたかったと思います。TypeScriptで開発しているときは、プロジェクトスコープ内に留まるため、JavaScriptのプライバシーはそれほど重要ではありません。まず第一に、元のコードは開発者にとって重要であり、積み重ねられた(JavaScript)コードではありません。
ローマンM.コス

1
JavaScriptライブラリを作成して公開しているのでない限り、変換されたコードは重要です
alexanderbird

あなたの答えは主題外です。
1

1

これは古い議論であることはわかっていますが、TypeScriptのプライベート変数とメソッドの可能性がある問題の解決策を、コンパイルされたJavaScriptクラスのパブリックインターフェイスに「漏出」することを共有すると役立つ場合があります。

私にとって、この問題は純粋に表面的なものです。つまり、DevToolsでインスタンス変数が表示されるときの視覚的な混乱がすべてです。私の修正は、プライベート宣言を別のクラス内でグループ化して、メインクラスでインスタンス化し、(ダブルアンダースコア)のprivateような名前で(ただし、JSでパブリックに表示される)変数に割り当てること__です。

例:

class Privates {
    readonly DEFAULT_MULTIPLIER = 2;
    foo: number;
    bar: number;

    someMethod = (multiplier: number = this.DEFAULT_MULTIPLIER) => {
        return multiplier * (this.foo + this.bar);
    }

    private _class: MyClass;

    constructor(_class: MyClass) {
        this._class = _class;
    }
}

export class MyClass {
    private __: Privates = new Privates(this);

    constructor(foo: number, bar: number, baz: number) {
        // assign private property values...
        this.__.foo = foo;
        this.__.bar = bar;

        // assign public property values...
        this.baz = baz;
    }

    baz: number;

    print = () => {
        console.log(`foo=${this.__.foo}, bar=${this.__.bar}`);
        console.log(`someMethod returns ${this.__.someMethod()}`);
    }
}

let myClass = new MyClass(1, 2, 3);

ときにmyClassインスタンスがデベロッパーツールで表示され、代わりにそのすべての「プライベート」のメンバーを見てのあなたがそれらをきちんと折りたたま内でグループ化された参照(非常に視覚的に適切にリファクタリング実際のコードに厄介にすることができます)本当に公共のものと混在し__プロパティ:

ここに画像の説明を入力してください


1
私はそれが好きです。きれいに見えます。

0

適切なプライベートプロパティを追加するための再利用可能なアプローチを次に示します。

/**
 * Implements proper private properties.
 */
export class Private<K extends object, V> {

    private propMap = new WeakMap<K, V>();

    get(obj: K): V {
        return this.propMap.get(obj)!;
    }

    set(obj: K, val: V) {
        this.propMap.set(obj, val);
    }
}

Client2つのプライベートプロパティを必要とするクラスがあるとします。

  • prop1: string
  • prop2: number

以下はそれを実装する方法です:

// our private properties:
interface ClientPrivate {
    prop1: string;
    prop2: number;
}

// private properties for all Client instances:
const pp = new Private<Client, ClientPrivate>();

class Client {
    constructor() {
        pp.set(this, {
            prop1: 'hello',
            prop2: 123
        });
    }

    someMethod() {
        const privateProps = pp.get(this);

        const prop1 = privateProps.prop1;
        const prop2 = privateProps.prop2;
    }
}

そして、必要なのが単一のプライベートプロパティだけの場合は、ClientPrivateその場合は何も定義する必要がないため、さらに簡単になります。

注目に値するのは、ほとんどの場合、クラスPrivateは読みやすい署名を提供するだけですが、直接使用する場合はWeakMapそうではありません。

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