Why can I access TypeScript private members when I shouldn't be able to?

JavascriptTypescript

Javascript Problem Overview


I'm looking at implementation of private members in TypeScript, and I find it a little confusing. Intellisense doesn't allow to access private member, but in pure JavaScript, it's all there. This makes me think that TS doesn't implement private members correctly. Any thoughts?

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

Javascript Solutions


Solution 1 - Javascript

Just as with the type checking, the privacy of members are only enforced within the compiler.

A private property is implemented as a regular property, and code outside the class is not allowed to access it.

To make something truly private inside the class, it can't be a member of the class, it would be a local variable created inside a function scope inside the code that creates the object. That would mean that you can't access it like a member of the class, i.e. using the this keyword.

Solution 2 - Javascript

JavaScript does support private variables.

function MyClass() {
    var myPrivateVar = 3;

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

In TypeScript this would be expressed like so:

class MyClass {

    doSomething: () => number;

    constructor() {
        var myPrivateVar = 3;

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

EDIT

This approach should only be used SPARINGLY where it is absolutely needed. For example if you need to cache a password temporarily.

There are performance costs to using this pattern (irrelevant of Javascript or Typescript) and should only be used where absolutely necessary.

Solution 3 - Javascript

Since TypeScript 3.8 will be released you will be able to declare private field which can’t be accessed or even detected outside of the containing class.

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 fields starts with # character

Please note that these private fields will be something different than fields marked with private keyword

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

Solution 4 - Javascript

Once support for WeakMap is more widely available there is an interesting technique detailed in example #3 here.

It allows for private data AND avoids the performance costs of Jason Evans example by allowing the data to be accessible from prototype methods instead of only instance methods.

The linked MDN WeakMap page lists browser support at Chrome 36, Firefox 6.0, IE 11, Opera 23, and 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)();
    }
  }
}

Solution 5 - Javascript

Thanks to Sean Feldman for the link to the official discussion on this issue - see his answer for the link.

I read the discussion he linked to, and here's a summary of the key points:

  • Suggestion: private properties in constructor
    • problems: can't access from prototype functions
  • Suggestion: private methods in constructor
    • problems: same as with properties, plus you lose the performance benefit of creating a function once per class in the prototype; instead you create a copy of the function for each instance
  • Suggestion: add boilerplate to abstract property access and enforce visibility
    • problems: major performance overhead; TypeScript is designed for large applications
  • Suggestion: TypeScript already wraps the constructor and prototype method definitions in a closure; put private methods and properties there
    • problems with putting private properties in that closure: they become static variables; there is not one per instance
    • problems with putting private methods in that closure: they do not have access to this without some sort of workaround
  • Suggestion: automatically mangle the private variable names
    • counter arguments: that's a naming convention, not a language construct. Mangle it yourself
  • Suggestion: Annotate private methods with @private so minifiers that recognize that annotation can effectively minify the method names
    • No significant counter arguments to this one

Overall counter-arguments to adding visibility support in emitted code:

  • the problem is that JavaScript itself doesn't have visibility modifiers - this isn't TypeScript's problem
  • there is already an established pattern in the JavaScript community: prefix private properties and methods with an underscore, which says "proceed at your own risk"
  • when TypeScript designers said that truly private properties and methods aren't "possible", they meant "not possible under our design constraints", specifically:
    • The emitted JS is idiomatic
    • Boilerplate is minimal
    • No additional overhead compared to normal JS OOP

Solution 6 - Javascript

I realize this is an older discussion but it might still be useful to share my solution to the problem of the supposedly private variables and methods in a TypeScript "leaking" out into the public interface of the compiled JavaScript class.

To me this issue is purely cosmetic, i.e. it's all about the visual clutter when an instance variable is viewed in DevTools. My fix is to group private declarations together inside another class that is then instantiated in the main class and assigned to a private (but still publicly visible in JS) variable with a name like __ (double underscore).

Example:

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

When the myClass instance is viewed in DevTools, instead of seeing all its "private" members intermixed with truly public ones (which can get very visually messy in properly refactored real-life code) you see them neatly grouped inside the collapsed __ property:

enter image description here

Solution 7 - Javascript

In TypeScript Private functions are only accessible inside the class. Like

enter image description here

And it will show an error when you try to access a private member. Here is the example:

enter image description here

> Note: It will be fine with javascript and both function are accessible > outside.

Solution 8 - Javascript

Here's reusable approach for adding proper private properties:

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

Let's say you have class Client somewhere that needs two private properties:

  • prop1: string
  • prop2: number

Below is how you implement it:

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

And if all you need is a single private property, then it gets even simpler, because you would not need to define any ClientPrivate in that case.

Worth noting, that for the most part, class Private just offers a nicely readable signature, whereas direct use of WeakMap does not.

Solution 9 - Javascript

In Summary - The type system will throw a warning message. But the private is a type system-specific feature, so it will go away at runtime.

Read an article I wrote about accessing TypeScript private variables here: https://szaranger.medium.com/stop-relying-on-private-to-hide-variables-in-typescript-3c45d25a58d0

Solution 10 - Javascript

Actually the answer to this question is rather simple.

You have this code:

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

In that code, you are mixing two different languages. This part is TypeScript:

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

and this part is JavaScript:

alert(new Test().member);

The private keyword in the Test class for member field is for TypeScript. Thus other classes within TypeScript cannot access the member field because the TypeScript compiler will not allow it. For example, if you tried this, it will not work:

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

It makes no difference whether you put private or public on the generated JavaScript. The generated JavaScript code will always be:

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

Therefore, you are able to do this because JavaScript will allow this:

alert(new Test().member);

This is not a rule carved in stone and there may be cases that I am not aware of but if you are using TypeScript, then why worry about what you are allowed to do using JavaScript; the idea is that you are no longer writing JavaScript so what you can/cannot do in JavaScript should not be something to worry about anymore. To me this worry would be the same as writing C# code and then saying how come I am able to change the private field in CIL or assembly language. I am not sure if CIL allows it or not, but that is not the point. The point is you just don't go poking around in what you can do with CIL after you write code in C#.

There may be cases where you write code in TypeScript but the public may use the generated JavaScript, a plugin perhaps, and you don't want them to break things, well in that case you would worry about it. For those cases, you would write your TypeScript code to make the fields private even on the JavaScript side. Other answers have already covered on how to do that.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSean FeldmanView Question on Stackoverflow
Solution 1 - JavascriptGuffaView Answer on Stackoverflow
Solution 2 - JavascriptMartinView Answer on Stackoverflow
Solution 3 - JavascriptPrzemek StrucińskiView Answer on Stackoverflow
Solution 4 - JavascriptRyan ThomasView Answer on Stackoverflow
Solution 5 - JavascriptalexanderbirdView Answer on Stackoverflow
Solution 6 - JavascriptCaspian CanuckView Answer on Stackoverflow
Solution 7 - JavascriptMuhammad AwaisView Answer on Stackoverflow
Solution 8 - Javascriptvitaly-tView Answer on Stackoverflow
Solution 9 - JavascriptSean AmarasingheView Answer on Stackoverflow
Solution 10 - JavascriptCodingYoshiView Answer on Stackoverflow