Waarom heb ik toegang tot privé-leden van TypeScript terwijl ik dat niet zou moeten kunnen?

Ik kijk naar de implementatie van privé-leden in TypeScript en ik vind het een beetje verwarrend. Intellisense staat geen toegang toe tot privéleden, maar in pure JavaScript is het er allemaal. Dit doet me denken dat TS privé-leden niet correct implementeert.
Enig idee?

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

Antwoord 1, autoriteit 100%

Net als bij typecontrole wordt de privacy van leden alleen afgedwongen binnen de compiler.

Een privé-eigenschap wordt geïmplementeerd als een normale eigenschap en code buiten de klas heeft geen toegang.

Om iets echt privé te maken binnen de klasse, kan het geen lid van de klasse zijn, het zou een lokale variabele zijn die is gemaakt in een functiebereik binnen de code die het object maakt. Dat zou betekenen dat je er geen toegang toe hebt als een lid van de klas, d.w.z. door het sleutelwoord thiste gebruiken.


Antwoord 2, autoriteit 37%

JavaScript ondersteunt privévariabelen.

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

In TypeScript zou dit als volgt worden uitgedrukt:

class MyClass {
    doSomething: () => number;
    constructor() {
        var myPrivateVar = 3;
        this.doSomething = function () {
            return myPrivateVar++;
        }
    }
}

BEWERKEN

Deze aanpak mag alleen SPAARMAALworden gebruikt waar het absoluut nodig is. Bijvoorbeeld als u tijdelijk een wachtwoord moet cachen.

Er zijn prestatiekosten verbonden aan het gebruik van dit patroon (niet relevant voor Javascript of Typescript) en mag alleen worden gebruikt waar dit absoluut noodzakelijk is.


Antwoord 3, autoriteit 20%

Sinds TypeScript 3.8 wordt uitgebracht, kunt u een privéveld declareren dat niet kan worden geopend of zelfs niet kan worden gedetecteerd buiten de bevattende klasse.

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.

Privévelden beginnen met #teken

Houd er rekening mee dat deze privévelden iets anders zullen zijn dan velden die zijn gemarkeerd met het private-zoekwoord

Ref. https://devblogs.microsoft.com/typescript/announcing-typescript- 3-8-bèta/


Antwoord 4, autoriteit 11%

Eenmaal ondersteuning voor WeakMapis meer algemeen beschikbaar is er een interessante techniek beschreven in voorbeeld #3 hier.

Het maakt privégegevens mogelijk EN vermijdt de prestatiekosten van het voorbeeld van Jason Evans door de gegevens toegankelijk te maken via prototypemethoden in plaats van alleen instantiemethoden.

De gekoppelde MDN WeakMap-pagina vermeldt browserondersteuning in Chrome 36, Firefox 6.0, IE 11, Opera 23 en 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)();
    }
  }
}

Antwoord 5, autoriteit 5%

Met dank aan Sean Feldman voor de link naar de officiële discussie over dit onderwerp – zie zijn antwoordvoor de link.

Ik heb de discussie gelezen waarnaar hij linkte, en hier is een samenvatting van de belangrijkste punten:

  • Suggestie:privé-eigendommen in aannemer
    • problemen:geen toegang vanaf prototypefuncties
  • Suggestie:privémethoden in constructor
    • problemen:hetzelfde als bij eigenschappen, plus je verliest het prestatievoordeel van het maken van een functie eenmaal per klasse in het prototype; in plaats daarvan maak je een kopie van de functie voor elke instantie
  • Suggestie:voeg standaardtekst toe aan abstracte eigendomstoegang en dwing zichtbaarheid af
    • problemen:grote prestatieoverhead; TypeScript is ontworpen voor grote toepassingen
  • Suggestie:TypeScript verpakt de constructor- en prototypemethodedefinities al in een afsluiting; zet daar privémethoden en eigenschappen
    • problemen met het plaatsen van privé-eigendommen in die afsluiting:het worden statische variabelen; er is er niet één per instantie
    • problemen met het plaatsen van privémethoden in die afsluiting:ze hebben geen toegang tot thiszonder een soort van tijdelijke oplossing
  • Suggestie:automatisch de namen van privévariabelen mangelen
    • tegenargumenten:dat is een naamgevingsconventie, geen taalconstructie. Verkruimel het zelf
  • Suggestie:annoteer privémethoden met @privatezodat minifiers die herkennen dat annotatie de methodenamen effectief kunnen verkleinen
    • Geen significante tegenargumenten voor deze

Algemene tegenargumenten voor het toevoegen van zichtbaarheidsondersteuning in uitgezonden code:

  • het probleem is dat JavaScript zelf geen zichtbaarheidsmodifiers heeft – dit is niet het probleem van TypeScript
  • er is al een vastgesteld patroon in de JavaScript-gemeenschap: voeg privé-eigenschappen en methoden toe met een onderstrepingsteken, dat zegt: “ga verder op eigen risico”
  • toen TypeScript-ontwerpers zeiden dat echt persoonlijke eigenschappen en methoden niet ‘mogelijk’ zijn, bedoelden ze ‘niet mogelijk onder onze ontwerpbeperkingen’, met name:
    • De uitgezonden JS is idiomatisch
    • Boilerplate is minimaal
    • Geen extra overhead in vergelijking met normale JS OOP

Antwoord 6

Ik realiseer me dat dit een oudere discussie is, maar het kan nog steeds nuttig zijn om mijn oplossing voor het probleem van de zogenaamd privévariabelen en methoden te delen in een TypeScript dat “lekt” naar de openbare interface van de gecompileerde JavaScript-klasse.

>

Voor mij is dit probleem puur cosmetisch, d.w.z. het gaat allemaal om de visuele rommel wanneer een instantievariabele wordt bekeken in DevTools. Mijn oplossing is om privédeclaraties te groeperen in een andere klasse die vervolgens wordt geïnstantieerd in de hoofdklasse en wordt toegewezen aan een private(maar nog steeds publiekelijk zichtbaar in JS) variabele met een naam als __(dubbel onderstrepingsteken).

Voorbeeld:

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

Wanneer de myClass-instantie wordt bekeken in DevTools, in plaats van dat al zijn “private” leden worden vermengd met echt openbare (wat visueel erg rommelig kan worden in correct gereconstrueerde real-life code), zie je ze netjes gegroepeerd in de samengevouwen eigenschap __:


Antwoord 7

In TypeScript zijn privéfuncties alleen toegankelijk binnen de klasse. Vind ik leuk

En er wordt een fout weergegeven wanneer u probeert toegang te krijgen tot een privélid. Hier is het voorbeeld:

Opmerking: het komt goed met javascript en beide functies zijn toegankelijk
buiten.


Antwoord 8

Hier is een herbruikbare aanpak voor het toevoegen van de juiste privé-eigendommen:

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

Stel dat je klasse Clientergens hebt die twee privé-eigenschappen nodig heeft:

  • prop1: string
  • prop2: number

Hieronder ziet u hoe u het implementeert:

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

En als alles wat je nodig hebt een enkel privé-eigendom is, wordt het nog eenvoudiger, omdat je in dat geval geen ClientPrivatehoeft te definiëren.

Het is vermeldenswaard dat klasse privatevoor het grootste deel gewoon een goed leesbare handtekening biedt, terwijl direct gebruik van WeakMapdat niet doet.


Antwoord 9

Eigenlijk is het antwoord op deze vraag vrij eenvoudig.

Je hebt deze code:

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

In die code meng je twee verschillende talen. Dit deel is TypeScript:

class Test{
  private member: any = "private member";
}

en dit deel is JavaScript:

alert(new Test().member);

Het privatetrefwoord in het veld Testvoor memberis voor TypeScript. Dus andere klassen binnen TypeScript hebben geen toegang tot het veld memberomdat de TypeScript-compiler dit niet toestaat. Als je dit bijvoorbeeld hebt geprobeerd, zal het niet werken:

class Test2 {
    constructor() {
        var test = new Test();
        test.member = "Cannot do this";
    }
}

Het maakt niet uit of je privateof publicop het gegenereerde JavaScript zet. De gegenereerde JavaScript-code is altijd:

var Test = (function () {
    function Test() {
        this.member = "private member";
    }
    return Test1;
}());

Daarom kunt u dit doen omdat JavaScript dit toestaat:

alert(new Test().member);

Dit is geen in steen gebeitelde regel en er kunnen gevallen zijn waarvan ik niet op de hoogte ben, maar als u TypeScript gebruikt, waarom zou u zich dan zorgen maken over wat u met JavaScript mag doen; het idee is dat je geen JavaScript meer schrijft, dus wat je wel/niet kunt doen in JavaScript zou niet langer iets moeten zijn om je zorgen over te maken. Voor mij zou deze zorg hetzelfde zijn als het schrijven van C#-code en dan zeggen hoe komt het dat ik het privéveld kan wijzigen in CILof assembler. Ik weet niet zeker of CIL het toestaat of niet, maar daar gaat het niet om. Het punt is dat je gewoon niet gaat rondneuzen in wat je met CIL kunt doen nadat je code in C# hebt geschreven.

Er kunnen gevallen zijn waarin u code schrijft in TypeScript, maar het publiek kan het gegenereerde JavaScript gebruiken, misschien een plug-in, en u wilt niet dat ze dingen kapot maken, in dat geval zou u zich daar zorgen over maken. In die gevallen zou u uw TypeScript-code schrijven om de velden privatezelfs aan de JavaScript-kant te maken. Andere antwoorden hebben al besproken hoe u dat kunt doen.

Other episodes