What are the differences between the private keyword and private fields in TypeScript?

后端 未结 2 903
终归单人心
终归单人心 2020-12-30 19:33

In TypeScript 3.8+, what are the differences between using the private keyword to mark a member private:

class PrivateKeywordClass {
    private         


        
2条回答
  •  生来不讨喜
    2020-12-30 20:23

    Use cases: #-private fields

    Preface:

    • TC39 proposal class-fields
    • Synonym terms: #-private, hard private, run-time private

    Compile-time and run-time privacy

    #-private fields provide compile-time and run-time privacy, which is not "hackable". It is a mechanism to prevent access to a member from outside the class body in any direct way.

    class A {
        #a: number;
        constructor(a: number) {
            this.#a = a;
        }
    }
    
    let foo: A = new A(42);
    foo.#a; // error, not allowed outside class bodies
    (foo as any).#bar; // still nope.
    

    Safe class inheritance

    #-private fields get a unique scope. Class hierarchies can be implemented without accidental overwrites of private properties with equal names.

    class A { 
        #a = "a";
        fnA() { return this.#a; }
    }
    
    class B extends A {
        #a = "b"; 
        fnB() { return this.#a; }
    }
    
    const b = new B();
    b.fnA(); // returns "a" ; unique property #a in A is still retained
    b.fnB(); // returns "b"
    

    TS compiler fortunately emits an error, when private properties are in danger of being overwriten (see this example). But due to the nature of a compile-time feature everything is still possible at run-time, given compile errors are ignored and/or emitted JS code utilized.

    External libraries

    Library authors can refactor #-private identifiers without causing a breaking change for clients. Library users on the other side are protected from accessing internal fields.

    JS API omits #-private fields

    Built-in JS functions and methods ignore #-private fields. This can result in a more predictable property selection at run-time. Examples: Object.keys, Object.entries, JSON.stringify, for..in loop and others (code sample; see also Matt Bierner's answer):

    class Foo {
        #bar = 42;
        baz = "huhu";
    }
    
    Object.keys(new Foo()); // [ "baz" ]
    

    Use cases: private keyword

    Preface:

    • private keyword in TS docs
    • Synonym terms: TS private, soft private, compile-time private

    Access to internal class API and state (compile-time only privacy)

    private members of a class are conventional properties at run-time. We can use this flexibility to access class internal API or state from the outside. In order to satisfy compiler checks, mechanisms like type assertions, dynamic property access or @ts-ignore may be used amongst others.

    Example with type assertion (as / <>) and any typed variable assignment:

    class A { 
        constructor(private a: number) { }
    }
    
    const a = new A(10);
    a.a; // TS compile error
    (a as any).a; // works
    const casted: any = a; casted.a // works
    

    TS even allows dynamic property access of a private member with an escape-hatch:

    class C {
      private foo = 10;
    }
    
    const res = new C()["foo"]; // 10, res has type number
    

    Where can private access make sense? (1) unit tests, (2) debugging/logging situations or (3) other advanced case scenarios with project-internal classes (open-ended list).

    Access to internal variables is a bit contradictory - otherwise you wouldn't have made them private in the first place. To give an example, unit tests are supposed to be black/grey boxes with private fields hidden as implementation detail. In practice though, there may be valid approaches from case to case.

    Available in all ES environments

    TS private modifiers can be used with all ES targets. #-private fields are only available for target ES2015/ES6 or higher. In ES6+, WeakMap is used internally as downlevel implementation (see here). Native #-private fields currently require target esnext.

    Consistency and compatibility

    Teams might use coding guidelines and linter rules to enforce the usage of private as the only access modifier. This restriction can help with consistency and avoid confusion with the #-private field notation in a backwards-compatible manner.

    If required, parameter properties (constructor assignment shorthand) are a show stopper. They can only be used with private keyword and there are no plans yet to implement them for #-private fields.

    Other reasons

    • private might provide better run-time performance in some down-leveling cases (see here).
    • There are no hard private class methods available in TS up to now.
    • Some people like the private keyword notation better

提交回复
热议问题