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


Answers:


43

私人关键字

TypeScript中的private关键字编译时注释。它告诉编译器一个属性只能在该类内部访问:

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 {
    constructor() {
        this.value = 1;
    }
}

由此,您可以看到为什么private关键字不提供任何运行时保护的原因:在生成的JavaScript中,它只是普通的JavaScript属性。

私人领域

私有字段可确保属性在运行时保持私有状态

class PrivateFieldClass {
    #value = 1;

    getValue() { return this.#value; }
}

const obj = new PrivateFieldClass();

// You can't access '#value' outside of class like this
obj.value === undefined // This is not the field you are looking for.
obj.getValue() === 1 // But the class itself can access the private field!

// Meanwhile, using a private field outside a class is a runtime syntax error:
obj.#value

// While trying to access the private fields of another class is 
// a runtime type error:
class Other {
    #value;

    getValue(obj) {
        return obj.#value // TypeError: Read of private field #value from an object which did not contain the field
    }
}

new Other().getValue(new PrivateKeywordClass());

如果尝试在类外使用私有字段,TypeScript还将输出编译时错误:

访问私有字段时出错

专用字段来自JavaScript提案,也可以在常规JavaScript中使用。

发出的JavaScript

如果您在TypeScript中使用私有字段,并且将JavaScript的较旧版本作为输出目标,例如es6es2018,TypeScript将尝试生成模拟私有字段运行时行为的代码。

class PrivateFieldClass {
    constructor() {
        _x.set(this, 1);
    }
}
_x = new WeakMap();

如果您定位esnext,TypeScript将发出私有字段:

class PrivateFieldClass {
    constructor() {
        this.#x = 1;
    }
    #x;
}

我应该使用哪一个?

这取决于您要实现的目标。

private关键字是一个很好的默认值。它完成了其设计要完成的任务,并已被TypeScript开发人员成功使用多年。而且,如果您已有代码库,则无需切换所有代码即可使用私有字段。如果您未定位目标esnext,则尤其如此,因为TS为私有字段发出的JS可能会对性能产生影响。另外请记住,私有字段与private关键字还有其他细微但重要的区别

但是,如果您需要实施运行时私有性或输出esnextJavaScript,则应使用私有字段。

还请记住,随着私有字段在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没有初始化的关键字私有财产不会产生在发出JavaScript中的财产申报:

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

    编译为:

    class PrivateKeywordClass {
        getValue() { return this.value; }
    }
    

    私有字段总是生成属性声明:

    class PrivateKeywordClass {
        #value?: string;
        getValue() { return this.#value; }
    }
    

    编译为(定位时esnext):

    class PrivateKeywordClass {
        #value;
        getValue() { return this.#value; }
    }
    

进一步阅读:


4

用例:#-私有字段

前言:

编译时运行时隐私

#-private字段提供编译时运行时的私密性,这不是“可破解的”。它是一种防止以任何直接方式从类体外访问成员的机制。

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"

private属性有被覆盖的危险时,TS编译器幸运地发出错误(请参见本示例)。但是,由于编译时功能的性质,在给定的编译错误被忽略和/或使用所发出的JS代码的情况下,在运行时一切仍然可能。

外部图书馆

库作者可以重构#-private标识符,而不会引起客户端的重大更改。另一侧的库用户受到保护,无法访问内部字段。

JS API忽略#-专用字段

内置的JS函数和方法会忽略#-private字段。这可以在运行时导致更可预测的属性选择。例如:Object.keysObject.entriesJSON.stringifyfor..in环路和其他(代码示例 ;也见太Bierner的答案):

class Foo {
    #bar = 42;
    baz = "huhu";
}

Object.keys(new Foo()); // [ "baz" ]

用例:private关键字

前言:

访问内部类API和状态(仅限编译时隐私)

private类的成员是运行时的常规属性。我们可以利用这种灵活性从外部访问类内部API或状态。为了满足编译器检查,@ts-ignore可以使用诸如类型声明,动态属性访问之类的机制。

类型声明(as/ <>)和any类型变量分配的示例:

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甚至允许private使用转义符对成员进行动态属性访问:

class C {
  private foo = 10;
}

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

私人访问在哪里有意义?(1)单元测试,(2)调试/记录情况或(3)其他具有项目内部类(开放式列表)的高级案例方案。

访问内部变量有点矛盾-否则您将不会private首先使它们 成为现实。举个例子,单元测试应该是黑/灰色框,其中的私有字段被隐藏为实现细节。但实际上,视情况而定,可能存在有效的方法。

在所有ES环境中均可用

TS private修饰符可以与所有ES目标一起使用。#-专用字段仅适用于target ES2015/ ES6或更高版本。在ES6 +中,WeakMap在内部用作下层实现(请参阅此处)。本机#专用字段当前需要target esnext

一致性和兼容性

团队可能会使用编码准则和linter规则来强制将private用作唯一的访问修饰符。此限制可以帮助保持一致,并#以向后兼容的方式避免与-private字段表示法混淆。

如果需要,参数属性(构造函数分配的简写形式)是显示停止符。它们只能与private关键字一起使用,并且尚无计划针对#-private字段实施它们。

其他原因

  • private在某些降级的情况下可能会提供更好的运行时性能(请参阅此处)。
  • 到目前为止,TS中没有可用的硬私有类方法。
  • 有些人更喜欢private关键字符号😊。

都注意

两种方法在编译时都会创建某种名义或品牌类型。

class A1 { private a = 0; }
class A2 { private a = 42; }

const a: A1 = new A2(); 
// error: "separate declarations of a private property 'a'"
// same with hard private fields

同样,它们都允许跨实例访问:类的实例A可以访问其他A实例的私有成员:

class A {
    private a = 0;
    method(arg: A) {
        console.log(arg.a); // works
    }
}

资料来源

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.