Quelles sont les différences entre le mot clé privé et les champs privés dans TypeScript?

Réponses:

43

Mot-clé privé

Le mot clé privé dans TypeScript est une annotation de compilation . Il indique au compilateur qu'une propriété ne doit être accessible qu'à l'intérieur de cette classe:

class PrivateKeywordClass {
    private value = 1;
}

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

Cependant, la vérification du temps de compilation peut être facilement contournée, par exemple en supprimant les informations de type:

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

Le privatemot clé n'est également pas appliqué lors de l'exécution

JavaScript émis

Lors de la compilation de TypeScript en JavaScript, le privatemot-clé est simplement supprimé:

class PrivateKeywordClass {
    private value = 1;
}

Devient:

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

De là, vous pouvez voir pourquoi le privatemot-clé n'offre aucune protection d'exécution: dans le JavaScript généré, c'est juste une propriété JavaScript normale.

Champs privés

Les champs privés garantissent que les propriétés restent privées lors de l'exécution :

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 générera également une erreur de temps de compilation si vous essayez d'utiliser un champ privé en dehors d'une classe:

Erreur lors de l'accès à un champ privé

Les champs privés proviennent d'une proposition JavaScript et fonctionnent également en JavaScript normal.

JavaScript émis

Si vous utilisez des champs privés dans TypeScript et ciblez des versions plus anciennes de JavaScript pour votre sortie, telles que es6ou es2018, TypeScript essaiera de générer du code qui émule le comportement d'exécution des champs privés

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

Si vous ciblez esnext, TypeScript émettra le champ privé:

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

Lequel devrais-je utiliser?

Cela dépend de ce que vous essayez de réaliser.

Le privatemot-clé est un bon défaut. Il accomplit ce pour quoi il a été conçu et a été utilisé avec succès par les développeurs TypeScript pendant des années. Et si vous avez une base de code existante, vous n'avez pas besoin de changer tout votre code pour utiliser des champs privés. Cela est particulièrement vrai si vous ne ciblez pas esnext, car le JS que TS émet pour les champs privés peut avoir un impact sur les performances. Gardez également à l'esprit que les champs privés présentent d'autres différences subtiles mais importantes par rapport au privatemot clé

Cependant, si vous devez appliquer la confidentialité d'exécution ou si vous générez du esnextJavaScript, vous devez utiliser des champs privés.

Gardez également à l'esprit que les conventions organisationnelles / communautaires sur l'utilisation de l'un ou de l'autre évolueront également à mesure que les champs privés se répandront dans les écosystèmes JavaScript / TypeScript.

Autres différences notables

  • Les champs privés ne sont pas retournés par Object.getOwnPropertyNamesdes méthodes similaires

  • Les champs privés ne sont pas sérialisés par JSON.stringify

  • Il y a des cas marginaux importants autour de l'héritage.

    TypeScript, par exemple, interdit de déclarer une propriété privée dans une sous-classe avec le même nom qu'une propriété privée dans la superclasse.

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

    Ce n'est pas vrai avec les champs privés:

    class Base {
        #value = 1;
    }
    
    class Sub extends Base {
        #value = 2; // Not an error
    }
    
  • Une privatepropriété privée de mot-clé sans initialiseur ne générera pas de déclaration de propriété dans le JavaScript émis:

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

    Compile vers:

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

    Alors que les champs privés génèrent toujours une déclaration de propriété:

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

    Compile vers (lors du ciblage esnext):

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

Lectures complémentaires:

Matt Bierner
la source
4

Cas d'utilisation: - #champs privés

Préface:

Confidentialité à la compilation et à l' exécution

#-Les champs privés fournissent une confidentialité au moment de la compilation et de l' exécution, qui n'est pas "piratable". C'est un mécanisme pour empêcher l'accès à un membre de l'extérieur du corps de classe de quelque manière directe .

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.

Héritage de classe sécurisé

#-Les champs privés ont une portée unique. Les hiérarchies de classes peuvent être implémentées sans écraser accidentellement des propriétés privées de noms égaux.

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"

Le compilateur TS émet heureusement une erreur, lorsque les privatepropriétés risquent d'être écrasées (voir cet exemple ). Mais en raison de la nature d'une fonctionnalité au moment de la compilation, tout est encore possible au moment de l'exécution, étant donné que les erreurs de compilation sont ignorées et / ou le code JS émis est utilisé.

Bibliothèques externes

Les auteurs de bibliothèque peuvent refactoriser les #identifiants privés sans provoquer de changement majeur pour les clients. Les utilisateurs de la bibliothèque de l'autre côté sont protégés contre l'accès aux champs internes.

L'API JS omet les #champs privés

Les fonctions et méthodes JS intégrées ignorent les #champs privés. Cela peut entraîner une sélection de propriétés plus prévisible au moment de l'exécution. Exemples: Object.keys, Object.entries, JSON.stringify, for..inboucle et autres ( exemple de code , voir aussi Matt Bierner réponse ):

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

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

Cas d'utilisation: privatemot - clé

Préface:

Accès à l'API et à l'état de la classe interne (confidentialité au moment de la compilation uniquement)

privateles membres d'une classe sont des propriétés classiques au moment de l'exécution. Nous pouvons utiliser cette flexibilité pour accéder à l'API interne de classe ou à l'état de l'extérieur. Afin de satisfaire les vérifications du compilateur, des mécanismes tels que les assertions de type, l'accès aux propriétés dynamiques ou @ts-ignorepeuvent être utilisés entre autres.

Exemple avec assertion de type ( as/ <>) et anyaffectation de variable typée:

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 permet même un accès dynamique aux propriétés d'un privatemembre avec un hayon :

class C {
  private foo = 10;
}

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

Où l'accès privé peut-il avoir un sens? (1) des tests unitaires, (2) des situations de débogage / journalisation ou (3) d'autres scénarios de cas avancés avec des classes internes au projet (liste ouverte).

L'accès aux variables internes est un peu contradictoire - sinon vous ne les auriez pas faites privateen premier lieu. Pour donner un exemple, les tests unitaires sont supposés être des boîtes noires / grises avec des champs privés cachés comme détails d'implémentation. En pratique cependant, il peut y avoir des approches valables d'un cas à l'autre.

Disponible dans tous les environnements ES

Les privatemodificateurs TS peuvent être utilisés avec toutes les cibles ES. #-Les champs privés ne sont disponibles que pour target ES2015/ ES6ou plus. Dans ES6 +, WeakMapest utilisé en interne comme implémentation de niveau inférieur (voir ici ). Les #champs natifs- privés nécessitent actuellement target esnext.

Cohérence et compatibilité

Les équipes peuvent utiliser des directives de codage et des règles de linter pour imposer l'utilisation de privatecomme seul modificateur d'accès. Cette restriction peut aider à la cohérence et éviter toute confusion avec la #notation de champ privé d'une manière rétrocompatible.

Si nécessaire, les propriétés des paramètres (raccourci d'affectation du constructeur) sont un bouchon d'exposition. Ils ne peuvent être utilisés qu'avec des privatemots clés et il n'est pas encore prévu de les implémenter pour les #champs privés.

Autres raisons

  • privatepeut fournir de meilleures performances d'exécution dans certains cas de mise à niveau inférieur (voir ici ).
  • Jusqu'à présent, aucune méthode de classe privée dure n'est disponible dans TS.
  • Certaines personnes préfèrent la privatenotation par mots clés 😊.

Remarque sur les deux

Les deux approches créent une sorte de type nominal ou de marque au moment de la compilation.

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

En outre, les deux autorisent l'accès inter-instances: une instance de classe Apeut accéder aux membres privés d'autres Ainstances:

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

Sources

ford04
la source