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

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

Answers:

Answer

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.

Answer

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.

Answer

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)();
    }
  }
}
Answer

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
Answer

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.

Answer

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

Answer

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/

Answer

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.

Tags

Recent Questions

Top Questions

Home Tags Terms of Service Privacy Policy DMCA Contact Us

©2020 All rights reserved.