private
在 TypeScript 3.8+ 中,使用关键字将成员标记为私有有什么区别:
class PrivateKeywordClass {
private value = 1;
}
并使用为 JavaScript 提议#
的私有字段:
class PrivateFieldClass {
#value = 1;
}
我应该更喜欢一个吗?
private
在 TypeScript 3.8+ 中,使用关键字将成员标记为私有有什么区别:
class PrivateKeywordClass {
private value = 1;
}
并使用为 JavaScript 提议#
的私有字段:
class PrivateFieldClass {
#value = 1;
}
我应该更喜欢一个吗?
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
关键字在运行时也不会强制执行
将 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。
如果您在 TypeScript 中使用私有字段,并且输出的目标是旧版本的 JavaScript,例如es6
or 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; }
}
#
-私有字段前言:
#
-private、hard private、run-time private#
-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.
#
-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 编译器会发出错误(请参阅此示例)。但是由于编译时特性的性质,在运行时一切仍然是可能的,因为编译错误被忽略和/或使用了发出的 JS 代码。
库作者可以重构#
私有标识符,而不会对客户端造成重大更改。另一边的图书馆用户受到保护,无法访问内部字段。
#
-private 字段内置 JS 函数和方法忽略#
-private 字段。这可以在运行时产生更可预测的属性选择。示例:Object.keys
, Object.entries
, JSON.stringify
, for..in
loop 和其他(代码示例;另请参见 Matt Bierner 的回答):
class Foo {
#bar = 42;
baz = "huhu";
}
Object.keys(new Foo()); // [ "baz" ]
private
关键字前言:
private
TS 文档中的关键字 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-hatchprivate
对成员进行动态属性访问:
class C {
private foo = 10;
}
const res = new C()["foo"]; // 10, res has type number
私人访问在哪里有意义?(1) 单元测试,(2) 调试/记录情况或 (3) 具有项目内部类的其他高级案例场景(开放式列表)。
访问内部变量有点矛盾——否则你一开始就不会制作它们 private
。举个例子,单元测试应该是黑色/灰色的盒子,私有字段隐藏为实现细节。但在实践中,可能存在针对具体情况的有效方法。
TSprivate
修饰符可用于所有 ES 目标。#
-private 字段仅适用于target
ES2015
/ES6
或更高版本。在 ES6+ 中,WeakMap
在内部用作下层实现(参见此处)。本机#
私有字段当前需要target
esnext
.
团队可能会使用编码指南和 linter 规则来强制使用private
作为唯一的访问修饰符。#
此限制有助于保持一致性,并以向后兼容的方式避免与-private 字段表示法混淆。
如果需要,参数属性(构造函数赋值简写)是一个显示停止器。它们只能与private
关键字一起使用,并且还没有计划为#
-private 字段实现它们。
private
在某些降级情况下可能会提供更好的运行时性能(请参见此处)。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
}
}
来源