admin管理员组

文章数量:1134232

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

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);
Share Improve this question edited Feb 26, 2019 at 13:08 Liam 29.5k28 gold badges137 silver badges200 bronze badges asked Oct 3, 2012 at 17:24 Sean FeldmanSean Feldman 25.9k8 gold badges62 silver badges85 bronze badges 6
  • You are wondering why IntelliSense doesn't give you the private member on the row with the alert()? – mylopeda Commented Oct 3, 2012 at 17:27
  • 10 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 typescriptlang.org/Playground alerts private member value. – Sean Feldman Commented Oct 3, 2012 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 Commented Dec 19, 2012 at 13:49
  • 1 Worth noting these days that typescript supports javascript's proposed #privateClassFields – snarf Commented Mar 27, 2021 at 22:00
  • 1 @SeanFeldman If you set noEmitOnErrors, 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 – Ruan Mendes Commented Oct 7, 2021 at 22:20
 |  Show 1 more comment

9 Answers 9

Reset to default 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.

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.

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/

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

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

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:

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.

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

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

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

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.

本文标签: javascriptWhy can I access TypeScript private members when I shouldn39t be able toStack Overflow