Pourquoi puis-je accéder aux membres privés TypeScript alors que je ne devrais pas pouvoir le faire?


108

Je regarde l'implémentation de membres privés dans TypeScript, et je trouve cela un peu déroutant. Intellisense ne permet pas d'accéder aux membres privés, mais en JavaScript pur, tout y est. Cela me fait penser que TS n'implémente pas correctement les membres privés. Des pensées?

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

Vous vous demandez pourquoi IntelliSense ne vous donne pas le membre privé sur la ligne avec l'alerte ()?
esrange

7
Nan. Je me demande pourquoi TS a un privé alors que ce n'est qu'un sucre pour l'intellisense, et pas vraiment pour le JavaScript dans lequel il compile. Ce code exécuté dans typescriptlang.org/Playground alerte la valeur du membre privé.
Sean Feldman

Comme mentionné, vous devez déclarer les éléments en tant que variable dans un contexte privé pour les rendre privés. Je suppose que dactylographié ne fait pas cela car il peut être inefficace par rapport à l'ajout au prototype. Cela perturbe également la définition de type (les membres privés ne font pas vraiment partie de la classe)
Shane

Si vous voulez de vraies variables privées qui existent sur le prototype, cela prend une certaine surcharge, mais j'ai écrit une bibliothèque appelée ClassJS qui fait exactement cela sur GitHub: github.com/KthProg/ClassJS .
KthProg

Réponses:


97

Tout comme pour la vérification de type, la confidentialité des membres n'est appliquée que dans le compilateur.

Une propriété privée est implémentée en tant que propriété standard et le code en dehors de la classe n'est pas autorisé à y accéder.

Pour rendre quelque chose de vraiment privé à l'intérieur de la classe, il ne peut pas être membre de la classe, ce serait une variable locale créée à l'intérieur d'une portée de fonction à l'intérieur du code qui crée l'objet. Cela signifierait que vous ne pouvez pas y accéder comme un membre de la classe, c'est-à-dire en utilisant le thismot - clé.


25
Il n'est pas inhabituel pour un programmeur javascript de mettre une variable locale dans un constructeur d'objet et de l'utiliser comme champ privé. Je suis surpris qu'ils n'aient pas soutenu quelque chose comme ça.
Eric

2
@Eric: Comme TypeScript utilise le prototype pour les méthodes au lieu d'ajouter des méthodes en tant que prototypes à l'intérieur du constructeur, une variable locale dans le constructeur n'est pas accessible à partir des méthodes. Il est peut-être possible de créer une variable locale à l'intérieur du wrapper de fonction pour la classe, mais je n'ai pas encore trouvé de moyen de le faire. Cependant, ce serait toujours une variable locale et non un membre privé.
Guffa

40
C'est quelque chose sur lequel j'ai fourni des commentaires. Je pense que cela devrait offrir la possibilité de créer un modèle de module révélateur, afin que les membres privés puissent rester privés et les membres publics puissent être accessibles en JavaScript. Il s'agit d'un modèle courant et fournirait la même accessibilité dans TS et JS.
John Papa

Il existe une solution que vous pouvez utiliser pour les membres statiques privés: basarat.com/2013/03/real-private-static-class-members-in.html
basarat

1
@BasaratAli: C'est une variable statique qui est disponible dans les méthodes de la classe, mais ce n'est pas un membre de la classe, c'est-à-dire que vous n'y accédez pas en utilisant le thismot - clé.
Guffa

37

JavaScript prend en charge les variables privées.

function MyClass() {
    var myPrivateVar = 3;

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

Dans TypeScript, cela s'exprimerait comme suit:

class MyClass {

    doSomething: () => number;

    constructor() {
        var myPrivateVar = 3;

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

ÉDITER

Cette approche doit être utilisée avec parcimonie uniquement lorsque cela est absolument nécessaire. Par exemple, si vous devez mettre en cache un mot de passe temporairement.

L'utilisation de ce modèle entraîne des coûts de performance (sans rapport avec Javascript ou Typescript) et ne doit être utilisé que lorsque cela est absolument nécessaire.


Est-ce que dactylographié ne le fait pas tout le temps en définissant var _thisune utilisation dans des fonctions étendues? Pourquoi auriez-vous des scrupules à le faire à la portée de la classe?
DrSammyD

Non. Var _ ceci est juste une référence à ceci.
Martin

2
Plus précisément pour les appeler des variables de constructeur, pas privées. Ceux-ci ne sont pas visibles dans les méthodes prototypes.
Roman M. Koss

1
oh oui, désolé, le problème était plutôt un autre problème, le fait que pour chaque instance que vous créez, doSomething sera créé à nouveau, car il ne fait pas partie de la chaîne de prototypes.
Barbu Barbu

1
@BarbuBarbu Oui, je suis d'accord. C'est un gros problème avec cette approche, et l'une des raisons pour lesquelles il devrait être évité.
Martin

11

Une fois que le support de WeakMap sera plus largement disponible, il y a une technique intéressante détaillée dans l'exemple n ° 3 ici .

Il permet des données privées ET évite les coûts de performance de l'exemple de Jason Evans en permettant aux données d'être accessibles à partir de méthodes prototypes plutôt que de méthodes d'instance uniquement.

La page MDN WeakMap liée répertorie la prise en charge des navigateurs sur Chrome 36, Firefox 6.0, IE 11, Opera 23 et 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)();
    }
  }
}

Je l'ai aimé! Fondamentalement, cela signifie cacher les propriétés privées dans une classe agrégée. Le plus amusant sera ... Que diriez-vous d'ajouter la prise en charge des protectedparamètres? : D
Roman M. Koss

2
@RamtinSoltani L'article lié indique qu'en raison du fonctionnement des cartes faibles, cela n'empêchera pas le garbage collection. Si quelqu'un voulait être plus sûr tout en utilisant cette technique, il pourrait implémenter son propre code d'élimination qui supprime la clé d'instance de classe de chacune des cartes faibles.
Ryan Thomas

1
Depuis la page MDN: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… . En revanche, les WeakMaps natifs contiennent des références «faibles» aux objets clés, ce qui signifie qu'ils n'empêchent pas le garbage collection au cas où il n'y aurait pas d'autre référence à l'objet clé. Cela évite également d'empêcher le garbage collection des valeurs de la carte.
Ryan Thomas

@RyanThomas C'est vrai, c'était un vieux commentaire que j'ai laissé il y a quelque temps. Contrairement à Maps, les WeakMaps ne causeront pas de fuites de mémoire. Il est donc prudent d'utiliser cette technique.
Ramtin Soltani

@RamtinSoltani Alors supprimez votre ancien commentaire?>
ErikE

10

Depuis la sortie de TypeScript 3.8, vous pourrez déclarer un champ privé qui ne peut pas être consulté ou même détecté en dehors de la classe contenant .

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.

Les champs privés commencent par # caractère

Veuillez noter que ces champs privés seront différents des champs marqués avec private mot-clé

Réf. https://devblogs.microsoft.com/typescript/announcing-typescript-3-8-beta/


4

Merci à Sean Feldman pour le lien vers la discussion officielle sur cette question - voir sa réponse pour le lien.

J'ai lu la discussion à laquelle il a lié, et voici un résumé des points clés:

  • Suggestion: propriétés privées dans le constructeur
    • problèmes: impossible d'accéder aux fonctions du prototype
  • Suggestion: méthodes privées dans le constructeur
    • problèmes: comme pour les propriétés, plus vous perdez l'avantage de performance de créer une fonction une fois par classe dans le prototype; à la place, vous créez une copie de la fonction pour chaque instance
  • Suggestion: ajoutez des règles standard à l'accès aux propriétés abstraites et appliquez la visibilité
    • problèmes: surcharge de performance majeure; TypeScript est conçu pour les grandes applications
  • Suggestion: TypeScript encapsule déjà les définitions de méthode du constructeur et du prototype dans une fermeture; y mettre des méthodes et des propriétés privées
    • problèmes de placement de propriétés privées dans cette fermeture: elles deviennent des variables statiques; il n'y en a pas un par instance
    • problèmes liés à l'insertion de méthodes privées dans cette fermeture: elles n'y ont pas accès thissans une sorte de solution de contournement
  • Suggestion: modifier automatiquement les noms des variables privées
    • counter arguments: c'est une convention de dénomination, pas une construction de langage. Mélangez-le vous-même
  • Suggestion: annoter les méthodes privées avec@private minificateurs qui reconnaissent que l'annotation peut réduire efficacement les noms de méthodes
    • Aucun contre-argument significatif à celui-ci

Contre-arguments globaux pour ajouter la prise en charge de la visibilité dans le code émis:

  • le problème est que JavaScript lui-même n'a pas de modificateurs de visibilité - ce n'est pas le problème de TypeScript
  • il existe déjà un modèle établi dans la communauté JavaScript: préfixez les propriétés privées et les méthodes par un trait de soulignement, qui dit "procédez à vos risques et périls"
  • lorsque les concepteurs de TypeScript ont dit que les propriétés et méthodes vraiment privées ne sont pas «possibles», elles signifiaient «pas possible sous nos contraintes de conception», en particulier:
    • Le JS émis est idiomatique
    • La plaque chauffante est minimale
    • Pas de surcharge supplémentaire par rapport à la POO JS normale

Si cette réponse provenait de cette conversation: typescript.codeplex.com/discussions/397651 -, veuillez fournir un lien: D
Roman M. Koss

1
Oui, c'est la conversation - mais j'ai lié à la réponse de Sean Feldman à cette question , où il fournit le lien. Puisqu'il a fait le travail de trouver le lien, je voulais lui en attribuer le mérite.
alexanderbird

2

Dans TypeScript, les fonctions privées ne sont accessibles qu'à l'intérieur de la classe. Comme

entrez la description de l'image ici

Et il affichera une erreur lorsque vous essayez d'accéder à un membre privé. Voici l'exemple:

entrez la description de l'image ici

Remarque: ce sera bien avec javascript et les deux fonctions sont accessibles à l'extérieur.


4
OP: "mais en JavaScript pur, tout est là" - Je ne pense pas que vous résolvez le problème que le JavaScript généré expose publiquement les fonctions "privées"
alexanderbird

1
@alexanderbird Je pense qu'il voulait dire que TypeScript suffit généralement. Lorsque nous développons en TypeScript, nous restons avec lui dans la portée du projet, donc la confidentialité de JavaScript n'est pas un gros problème. Parce que tout d'abord, le code original est important pour le développeur, pas celui transpilé (JavaScript).
Roman M. Koss

1
À moins que vous n'écriviez et ne publiiez une bibliothèque JavaScript, le code transpilé importe
alexanderbird

votre réponse est hors sujet.
canbax le

1

Je me rends compte que c'est une discussion plus ancienne, mais il pourrait encore être utile de partager ma solution au problème des variables et méthodes supposées privées dans un TypeScript "fuyant" dans l'interface publique de la classe JavaScript compilée.

Pour moi, ce problème est purement cosmétique, c'est-à-dire qu'il s'agit de l'encombrement visuel lorsqu'une variable d'instance est visualisée dans DevTools. Ma solution consiste à regrouper les déclarations privées dans une autre classe qui est ensuite instanciée dans la classe principale et affectée à une privatevariable (mais toujours visible publiquement dans JS) avec un nom comme __(double tiret bas).

Exemple:

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);

Lorsque l' myClassinstance est visualisée dans DevTools, au lieu de voir tous ses membres "privés" mélangés avec des membres vraiment publics (ce qui peut devenir très désordonné visuellement dans un code réel correctement refactoré), vous les voyez parfaitement regroupés dans la __propriété réduite :

entrez la description de l'image ici


1
Je l'aime. Ça a l'air propre.

0

Voici une approche réutilisable pour ajouter des propriétés privées appropriées:

/**
 * 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);
    }
}

Disons que vous avez une classe Clientquelque part qui a besoin de deux propriétés privées:

  • prop1: string
  • prop2: number

Voici comment vous l'implémentez:

// 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;
    }
}

Et si vous n'avez besoin que d'une propriété privée unique, cela devient encore plus simple, car vous n'auriez pas besoin d'en définir ClientPrivatedans ce cas.

Il convient de noter que, pour la plupart, la classe Privateoffre simplement une signature bien lisible, contrairement à l'utilisation directe de WeakMap.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.