TypeScript 中的 private 关键字和 private 字段有啥区别?
Posted
技术标签:
【中文标题】TypeScript 中的 private 关键字和 private 字段有啥区别?【英文标题】:What are the differences between the private keyword and private fields in TypeScript?TypeScript 中的 private 关键字和 private 字段有什么区别? 【发布时间】:2020-04-25 17:39:44 【问题描述】:在 TypeScript 3.8+ 中,使用 private
关键字将成员标记为私有有什么区别:
class PrivateKeywordClass
private value = 1;
并使用#
私有字段proposed for javascript:
class PrivateFieldClass
#value = 1;
我应该更喜欢一个吗?
【问题讨论】:
The future of the "private" keyword in TypeScript. 【参考方案1】:私人关键字
TypeScript 中的private keyword 是一个编译时注解。它告诉编译器一个属性只能在该类中访问:
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
keyword 也不会在运行时强制执行
发出的 JavaScript
将 TypeScript 编译为 JavaScript 时,private
关键字被简单地删除:
class PrivateKeywordClass
private value = 1;
变成:
class PrivateKeywordClass
constructor()
this.value = 1;
由此,您可以看到为什么private
关键字不提供任何运行时保护:在生成的 JavaScript 中,它只是一个普通的 JavaScript 属性。
私人领域
Private fields 确保属性在运行时保持私有:
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 proposal,也可以在普通 JavaScript 中使用。
发出的 JavaScript
如果您在 TypeScript 中使用私有字段并且输出的目标是旧版本的 JavaScript,例如 es6
或 es2018
,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
关键字有其他细微但重要的区别
但是,如果您需要强制执行运行时隐私或输出 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
关键字私有属性将不会在发出的 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;
延伸阅读:
The future of the "private" keyword TypeScript PR that added private fields【讨论】:
【参考方案2】:用例:#
-私有字段
前言:
TC39 proposal class-fields 同义词:#
-private、硬私有、运行时私有
编译时和运行时隐私
#
-private 字段提供编译时和运行时隐私,这是不可“破解”的。这是一种防止从类体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.
安全类继承
#
-private 字段具有唯一的范围。可以在不意外覆盖同名私有属性的情况下实现类层次结构。
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 编译器会发出错误(请参阅this example)。但是由于编译时特性的性质,在运行时一切仍然是可能的,因为编译错误被忽略和/或发出的 JS 代码被利用。
外部库
库作者可以重构#
-private 标识符,而不会对客户端造成重大更改。另一边的图书馆用户受到保护,无法访问内部字段。
JS API 省略了#
-private 字段
内置的 JS 函数和方法忽略 #
-private 字段。这可以在运行时产生更可预测的属性选择。示例:Object.keys
、Object.entries
、JSON.stringify
、for..in
循环和其他(code sample;另见 Matt Bierner 的 answer):
class Foo
#bar = 42;
baz = "huhu";
Object.keys(new Foo()); // [ "baz" ]
用例:private
关键字
前言:
private
keyword in TS docs
同义词:TS 私有、软私有、编译时私有
访问内部类 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 甚至允许使用escape-hatch 对private
成员进行动态属性访问:
class C
private foo = 10;
const res = new C()["foo"]; // 10, res has type number
私人访问在哪里有意义? (1) 单元测试,(2) 调试/记录情况或 (3) 具有项目内部类的其他高级案例场景(开放式列表)。
访问内部变量有点矛盾——否则你一开始就不会把它们设为private
。举个例子,单元测试应该是黑色/灰色的盒子,私有字段隐藏为实现细节。但在实践中,可能存在针对不同情况的有效方法。
适用于所有 ES 环境
TS private
修饰符可用于所有 ES 目标。 #
-private 字段仅适用于 target
ES2015
/ES6
或更高版本。在 ES6+ 中,WeakMap
在内部用作下层实现(参见here)。原生#
-private 字段目前需要target
esnext
。
一致性和兼容性
团队可能会使用编码指南和 linter 规则来强制使用 private
作为唯一的访问修饰符。此限制有助于保持一致性,并以向后兼容的方式避免与 #
-private 字段表示法混淆。
如果需要,parameter properties(构造函数分配简写)是一个显示停止器。它们只能与private
关键字一起使用,并且no plans 尚未为#
-private 字段实现它们。
其他原因
private
可能在某些降级情况下提供更好的运行时性能(请参阅here)。
到目前为止,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
来源
https://devblogs.microsoft.com/typescript/announcing-typescript-3-8-beta/#ecmascript-private-fields https://github.com/microsoft/TypeScript/issues/31670 https://github.com/Microsoft/TypeScript/pull/30829【讨论】:
以上是关于TypeScript 中的 private 关键字和 private 字段有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章