TypeScript中的private关键字和private字段之间有什么区别?

TypeScript中的private关键字和private字段之间有什么区别?,typescript,class,private,encapsulation,class-fields,Typescript,Class,Private,Encapsulation,Class Fields,在TypeScript 3.8+中,使用private关键字标记成员private有什么区别: class PrivateKeywordClass { private value = 1; } 并使用#专用字段: 我应该选择一个而不是另一个吗?Private关键字 in-TypeScript是编译时注释。它告诉编译器属性只能在该类中访问: class PrivateKeywordClass { private value = 1; } const obj = new Priv

在TypeScript 3.8+中,使用
private
关键字标记成员private有什么区别:

class PrivateKeywordClass {
    private value = 1;
}
并使用
#
专用字段:

我应该选择一个而不是另一个吗?

Private关键字 in-TypeScript是编译时注释。它告诉编译器属性只能在该类中访问:

class PrivateKeywordClass {
    private value = 1;
}

const obj = new PrivateKeywordClass();
obj.value // compiler error: Property 'value' is private and only accessible within class 'PrivateKeywordClass'.
但是,可以轻松绕过编译时检查,例如通过丢弃类型信息:

const obj = new PrivateKeywordClass();
(obj as any).value // no compile error
private
关键字也不会在运行时强制执行

发出的JavaScript 将TypeScript编译为JavaScript时,只需删除
private
关键字:

class PrivateKeywordClass {
    private value = 1;
}
变成:

class PrivateKeywordClass{
构造函数(){
这个值=1;
}
}
从这里,您可以看到为什么
private
关键字不提供任何运行时保护:在生成的JavaScript中,它只是一个普通的JavaScript属性

私人领域 确保属性在运行时保持私有

class PrivateFieldClass{
#数值=1;
getValue(){返回此值。#value;}
}
const obj=新的PrivateFieldClass();
//您不能像这样在类外访问“#value”
obj.value==未定义//这不是您要查找的字段。
obj.getValue()==1//但是类本身可以访问私有字段!
//同时,在类之外使用私有字段是一个运行时语法错误:
目标值
//尝试访问另一个类的私有字段时
//运行时类型错误:
其他类{
#价值观;
getValue(obj){
返回obj.#value//TypeError:从不包含该字段的对象中读取私有字段#value
}
}
新的Other().getValue(新的PrivateKeywordClass());
如果尝试在类之外使用私有字段,TypeScript还将输出编译时错误:

私有字段来自于,也可以在普通JavaScript中使用

发出的JavaScript 如果您在TypeScript中使用私有字段,并针对较旧版本的JavaScript进行输出,例如
es6
es2018
,TypeScript将尝试生成模拟私有字段运行时行为的代码

class PrivateFieldClass{
构造函数(){
_x、 设置(本,1);
}
}
_x=新的WeakMap();
如果您的目标是
esnext
,则TypeScript将发出专用字段:

class PrivateFieldClass{
构造函数(){
这个#x=1;
}
#x;
}
我应该用哪一个? 这取决于你想要达到的目标

private
关键字是一个很好的默认值。它完成了设计的目标,多年来一直被TypeScript开发人员成功使用。如果您有一个现有的代码库,则不需要将所有代码切换为使用私有字段。如果您不是针对
esnext
,这一点尤其正确,因为TS为私有字段发出的JS可能会影响性能。还要记住,私有字段与
private
关键字还有其他细微但重要的区别

但是,如果您需要强制运行时私有性或输出
esnext
JavaScript,则应该使用私有字段

还请记住,随着私有字段在JavaScript/TypeScript生态系统中变得越来越广泛,组织/社区关于使用其中一个的约定也将不断发展

其他值得注意的差异
  • Object.getOwnPropertyNames
    和类似方法不会返回私有字段

  • 私有字段不是由JSON.stringify序列化的

  • 关于继承,有一些重要的边缘案例

    例如,TypeScript禁止在子类中声明与超类中私有属性同名的私有属性

    class Base {
        private value = 1;
    }
    
    class Sub extends Base {
        private value = 2; // Compile error:
    }
    
    对于私有字段,情况并非如此:

    class Base {
        #value = 1;
    }
    
    class Sub extends Base {
        #value = 2; // Not an error
    }
    
  • 没有初始值设定项的
    private
    关键字private property将不会在发出的JavaScript中生成属性声明:

    class PrivateKeywordClass {
        private value?: string;
        getValue() { return this.value; }
    }
    
    汇编至:

    class PrivateKeywordClass{
    getValue(){返回this.value;}
    }
    
    而私有字段始终生成属性声明:

    class PrivateKeywordClass {
        #value?: string;
        getValue() { return this.#value; }
    }
    
    编译到(当目标为esnext时):

    class PrivateKeywordClass{
    #价值观;
    getValue(){返回此值。#value;}
    }
    
进一步阅读:
用例:
#
-私有字段 前言:

  • 同义词:私有、硬私有、运行时私有
编译时和运行时隐私
#
-私有字段提供编译时和运行时隐私,这是不可“黑客”的。它是一种防止从类主体外部访问成员的机制

安全类继承
#
-私有字段具有唯一的作用域。可以实现类层次结构,而不会意外覆盖具有相同名称的私有属性

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"
幸运的是,当
private
属性有被覆盖的危险时,TS编译器会发出一个错误(请参阅)。但是由于编译时特性的性质,在运行时一切都是可能的,因为编译错误会被忽略和/或使用发出的JS代码

外部图书馆 库作者可以重构
-私有标识符,而不会对客户端造成破坏性的更改。另一端的库用户受到保护,无法访问内部字段

JSAPI省略了
#
-私有字段 内置JS函数和方法忽略
#
-私有字段。这会导致在运行时选择更可预测的属性。示例:
Object.keys
Object.entries
JSON.stringify
for..in
循环和其他
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.
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"
class Foo {
    #bar = 42;
    baz = "huhu";
}

Object.keys(new Foo()); // [ "baz" ]
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
class C {
  private foo = 10;
}

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