121

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);
Liam
  • 27,717
  • 28
  • 128
  • 190
Sean Feldman
  • 23,443
  • 7
  • 55
  • 80
  • You are wondering why IntelliSense doesn't give you the private member on the row with the alert()? – mylopeda Oct 03 '12 at 17:27
  • 8
    Nope. I am wondering why TS has a private when that is only a sugar for intellisense, and not really for the JavaScript it compiles to. This code executed in http://www.typescriptlang.org/Playground/ alerts private member value. – Sean Feldman Oct 03 '12 at 17:36
  • As mentioned, you must declare items as a variable in a private context to have them private. I am guessing typescript does not do this because it can be inefficient vs adding to the prototype. It also messes with the type definition (the private members aren't really part of the class) – Shane Dec 19 '12 at 13:49
  • If you want real private variables that exist on the prototype, it does take some overhead, but I've written a library called ClassJS that does just that on GitHub: https://github.com/KthProg/ClassJS. – KthProg Apr 12 '17 at 01:11
  • 1
    Worth noting these days that typescript supports javascript's proposed `#privateClassFields` – snarf Mar 27 '21 at 22:00
  • 1
    @SeanFeldman If you set [noEmitOnErrors](https://www.typescriptlang.org/tsconfig#noEmitOnError), then the JS won't be generated and it won't allow access to private variables :). Since I'm adding this comment in 2021, we can use [private JS variables now](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Private_class_fields) – Ruan Mendes Oct 07 '21 at 22:20

10 Answers10

109

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.

Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • 25
    It's not unusual for a javascript programmer to put a local variable in an object constructor and use it as a private field though. I'm surprised they didn't support something like this. – Eric Rini Oct 19 '12 at 00:15
  • 2
    @Eric: As TypeScript uses the prototype for methods instead of adding methods as prototypes inside the constructor, a local variable in the constructor isn't reachable from the methods. It might be possible to make a local variable inside the function wrapper for the class, but I haven't yet found a way to do that. However, that would still be a local variable, and not a private member. – Guffa Oct 19 '12 at 08:06
  • 41
    This is something I have been providing feedback on. I believe it should offer the option of creating a Revealing Module Pattern, so the private members can remain private and the public ones can be accessible in JavaScript. This is a common pattern and would provide the same accessibility in TS and JS. – John Papa Dec 19 '12 at 01:42
  • There is a solution you can use for private static members : https://web.archive.org/web/20140521231105/https://basarat.com/2013/03/real-private-static-class-members-in.html – basarat Mar 18 '13 at 05:18
  • 1
    @BasaratAli: That is a static variable that is available inside the methods of the class, but it's not a member of the class, i.e. you don't access it using the `this` keyword. – Guffa Mar 18 '13 at 09:31
  • Actually static are via `classname.member`, but since its private you don't want it to be accessible on the constructor function (classname). Nonetheless it can still be accessed by extending the module. So I guess its useless :) – basarat Mar 18 '13 at 11:24
  • @BasaratAli: Extending the module doesn't give access to the local variable. It's available in the scope where the methods are created, not in the object, so adding more methods to the object from outside that scope doesn't give them access to the variable. – Guffa Mar 18 '13 at 12:12
  • @JohnPapa The problem with revealing module pattern is that the private variables will not be available to the constructor and only in the functions in the prototype, Or, they will be available to the constructor and not to the functions in the prototype. (as per my understanding) – basarat Mar 21 '13 at 01:43
  • @JhonPapa also read this huge topic about this issue: https://typescript.codeplex.com/discussions/397651 It says that Revealing Module Pattern is a problem of to many memory being kept for single closure. End of discussion leads to name mangling, like minification. So does google JS API. – Roman M. Koss Dec 07 '16 at 00:45
  • The problem with Revealing Module Pattern is that you need prototypes for proper JS inheritance. I've written a library that accomplishes this called ClassJS, which you can see here on GitHub: https://github.com/KthProg/ClassJS. – KthProg Apr 12 '17 at 01:10
  • This answer is now outdated - see [here](https://stackoverflow.com/a/59712324/12299000). – kaya3 Feb 26 '23 at 11:11
39

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.

Jason Evans
  • 28,906
  • 14
  • 90
  • 154
Martin
  • 15,820
  • 4
  • 47
  • 56
  • Doesn't typescript do this ALL the time by setting `var _this` for use in scoped functions? Why would you have qualms doing it at the class scope? – DrSammyD Mar 24 '16 at 17:22
  • No. var _this is just a reference to this. – Martin Mar 24 '16 at 20:44
  • Can you add a reference for the performance cost? Imho it would only be a performance cost when you initialize a huge number of objects from that class. – Peter Briers Sep 27 '16 at 12:37
  • Peter -- I don't dispute this. It has been well documented that this pattern has a memory and performance impact. That is not to say there are not rare cases that this pattern may be beneficial. – Martin Sep 27 '16 at 13:32
  • 2
    More accurately to call them constructor variables, not private. Those are not visible in prototype methods. – Roman M. Koss Dec 07 '16 at 00:55
  • that's not private for the entire class, but only to the local method, in your case the constructor. A js private should be accessed by all class methods, which in this case it's not possible. The weakmap way is the only solution available. – stackoverflow Mar 28 '18 at 09:13
  • @BarbuBarbu In the example above the method implementations are in the constructor, giving them access to the "private" fields. – Martin Mar 28 '18 at 09:22
  • 1
    oh yeah, sorry the problem was other one instead, the fact that for every instance you create, doSomething will be created again, because it's not part of the prototype chain. – stackoverflow Mar 28 '18 at 09:28
  • 1
    @BarbuBarbu Yes I agree. This is a BIG issue with this approach, and one of the reasons it should be avoided. – Martin Mar 28 '18 at 09:31
29

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/

Przemek Struciński
  • 4,990
  • 1
  • 28
  • 20
  • `Can’t be accessed or detected even by JS users! Sometimes we call this hard privacy.` This is a game changer, thanks for sharing – Fennec Apr 04 '22 at 23:26
11

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)();
    }
  }
}
Ryan Thomas
  • 479
  • 5
  • 16
  • I liked it! Basically it means hide private properties into aggregated class. The most fun will be... How about add support for `protected`parameters? :D – Roman M. Koss Dec 07 '16 at 01:07
  • 2
    @RamtinSoltani The linked article stats that due to how weakmaps work, this won't prevent garbage collection. If someone wanted to be extra safe while using this technique they could implement their own disposal code that deletes the class instance key from each of the weakmaps. – Ryan Thomas Jan 17 '18 at 15:50
  • 1
    From the MDN page: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap. By contrast, native WeakMaps hold "weak" references to key objects, which means that they do not prevent garbage collection in case there would be no other reference to the key object. This also avoids preventing garbage collection of values in the map. – Ryan Thomas Jan 17 '18 at 15:54
  • @RyanThomas True, that was an old comment I left a while ago. The WeakMaps, as opposed to Maps, won’t cause memory leaks. So it’s safe to use this technique. – Ramtin Soltani Jan 18 '18 at 07:18
  • @RamtinSoltani So delete your old comment?> – ErikE May 01 '20 at 01:02
5

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
Community
  • 1
  • 1
alexanderbird
  • 3,847
  • 1
  • 26
  • 35
  • If this answer was from this conversation: https://typescript.codeplex.com/discussions/397651 - , please provide a link :D – Roman M. Koss Dec 07 '16 at 00:49
  • 2
    Yes, that is the conversation - but I linked to [Sean Feldman's answer to this question](http://stackoverflow.com/a/12713927/3012550), where he provides the link. Since he did the work of finding the link, I wanted to give him the credit. – alexanderbird Dec 07 '16 at 01:03
1

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

Caspian Canuck
  • 1,460
  • 1
  • 13
  • 19
0

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.

Muhammad Awais
  • 4,238
  • 1
  • 42
  • 37
  • 4
    OP: "but in pure JavaScript, it's all there" - I don't think you address the issue that the generated JavaScript exposes the "private" functions publically – alexanderbird Oct 04 '16 at 15:24
  • 2
    @alexanderbird I think that he wanted to say that TypeScript is enough usually. When we are developing in TypeScript, we stay with it in the project scope, so privacy from JavaScript is not a big deal. Because first of all, the original code matters for developer, not transpiled (JavaScript) ones. – Roman M. Koss Dec 07 '16 at 00:53
  • 2
    Unless you're writing and publishing a JavaScript library, then the transpiled code does matter – alexanderbird Dec 07 '16 at 01:05
  • your answer is off-topic. – canbax Jan 16 '20 at 07:25
0

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.

vitaly-t
  • 24,279
  • 15
  • 116
  • 138
-1

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.

CodingYoshi
  • 25,467
  • 4
  • 62
  • 64
-1

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

Sean Amarasinghe
  • 638
  • 1
  • 6
  • 8