TypeScript 中的私有关键字和私有字段有什么区别?
What are the differences between the private keyword and private fields in TypeScript?
在 TypeScript 3.8+ 中,使用 private
关键字将成员标记为私有有什么区别:
class PrivateKeywordClass {
private value = 1;
}
并使用 #
私有字段 proposed for JavaScript:
class PrivateFieldClass {
#value = 1;
}
我应该更喜欢一个吗?
私人关键字
TypeScript 中的 private keyword 是一个 编译时 注释。它告诉编译器 属性 应该只能在 class 内部访问:
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 属性。
私人领域
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());
如果您尝试在 class:
之外使用私有字段,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,那么您应该使用私有字段。
另请记住,organization/community 关于使用其中一个或另一个的约定也会随着私有领域在 JavaScript/TypeScript 生态系统中变得更加广泛而发展
其他注意事项
Object.getOwnPropertyNames
和类似方法不返回私有字段
私有字段没有被JSON.stringify
序列化
关于继承有一些重要的边缘案例。
例如,TypeScript 禁止在 subclass 中声明私有 属性,其名称与 superclass.[=43= 中的私有 属性 同名]
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; }
}
延伸阅读:
用例:#
-私有字段
前言:
- TC39 proposal class-fields
- 同义词:
#
-private, hard private, 运行-time private
编译时和运行-时间隐私
#
-私有字段提供编译时和运行-时间隐私,这不是"hackable"。这是一种防止从 class 正文 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.
安全class继承
#
-私有字段获得唯一范围。 Class 可以实现层次结构而不会意外覆盖具有相同名称的私有属性。
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)。但由于编译时特性的性质,在 运行 时一切仍然可能,考虑到编译错误被忽略 and/or 使用发出的 JS 代码。
外部图书馆
库作者可以重构 #
-private 标识符,而不会对客户端造成重大更改。另一端的图书馆用户无法访问内部字段。
JS API省略#
-私有字段
内置 JS 函数和方法忽略 #
-私有字段。这可以在 运行 时间产生更可预测的 属性 选择。示例:Object.keys
、Object.entries
、JSON.stringify
、for..in
循环和其他 (code sample; see also Matt Bierner's ):
class Foo {
#bar = 42;
baz = "huhu";
}
Object.keys(new Foo()); // [ "baz" ]
用例:private
关键字
前言:
private
keyword in TS docs
- 同义词:TS private、soft private、compile-time private
访问内部 class API 和状态(仅编译时隐私)
private
class 的成员是 运行 时间的常规属性。我们可以利用这种灵活性从外部访问 class 内部 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) debugging/logging 情况或 (3) 其他具有项目内部 classes 的高级案例场景(开放式列表)。
对内部变量的访问有点矛盾——否则你一开始就不会private
。举个例子,单元测试应该是 black/grey 框,其中隐藏了私有字段作为实现细节。但在实践中,根据具体情况可能会有有效的方法。
在所有 ES 环境中可用
TS private
修饰符可用于所有 ES 目标。 #
-私有字段仅适用于 target
ES2015
/ES6
或更高版本。在 ES6+ 中,WeakMap
在内部用作下层实现(参见 here)。原生 #
-私有字段目前需要 target
esnext
.
一致性和兼容性
团队可能会使用编码指南和 linter 规则来强制使用 private
作为唯一的访问修饰符。此限制有助于保持一致性并避免以向后兼容的方式与 #
-私有字段表示法混淆。
如果需要,parameter properties (constructor assignment shorthand) are a show stopper. They can only be used with private
keyword and there are no plans 尚未为 #
-私有字段实施它们。
其他原因
private
在某些降级情况下可能会提供更好的 运行 时间性能(参见 here)。
- 到目前为止,TS 中没有硬私有 class 方法。
- 有些人更喜欢
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
此外,两者都允许跨实例访问:class A
的一个实例可以访问其他 A
个实例的私有成员:
class A {
private a = 0;
method(arg: A) {
console.log(arg.a); // works
}
}
来源
在 TypeScript 3.8+ 中,使用 private
关键字将成员标记为私有有什么区别:
class PrivateKeywordClass {
private value = 1;
}
并使用 #
私有字段 proposed for JavaScript:
class PrivateFieldClass {
#value = 1;
}
我应该更喜欢一个吗?
私人关键字
TypeScript 中的 private keyword 是一个 编译时 注释。它告诉编译器 属性 应该只能在 class 内部访问:
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 属性。
私人领域
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());
如果您尝试在 class:
之外使用私有字段,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,那么您应该使用私有字段。
另请记住,organization/community 关于使用其中一个或另一个的约定也会随着私有领域在 JavaScript/TypeScript 生态系统中变得更加广泛而发展
其他注意事项
Object.getOwnPropertyNames
和类似方法不返回私有字段私有字段没有被
JSON.stringify
序列化
关于继承有一些重要的边缘案例。
例如,TypeScript 禁止在 subclass 中声明私有 属性,其名称与 superclass.[=43= 中的私有 属性 同名]
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; } }
延伸阅读:
用例:#
-私有字段
前言:
- TC39 proposal class-fields
- 同义词:
#
-private, hard private, 运行-time private
编译时和运行-时间隐私
#
-私有字段提供编译时和运行-时间隐私,这不是"hackable"。这是一种防止从 class 正文 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.
安全class继承
#
-私有字段获得唯一范围。 Class 可以实现层次结构而不会意外覆盖具有相同名称的私有属性。
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)。但由于编译时特性的性质,在 运行 时一切仍然可能,考虑到编译错误被忽略 and/or 使用发出的 JS 代码。
外部图书馆
库作者可以重构 #
-private 标识符,而不会对客户端造成重大更改。另一端的图书馆用户无法访问内部字段。
JS API省略#
-私有字段
内置 JS 函数和方法忽略 #
-私有字段。这可以在 运行 时间产生更可预测的 属性 选择。示例:Object.keys
、Object.entries
、JSON.stringify
、for..in
循环和其他 (code sample; see also Matt Bierner's
class Foo {
#bar = 42;
baz = "huhu";
}
Object.keys(new Foo()); // [ "baz" ]
用例:private
关键字
前言:
private
keyword in TS docs- 同义词:TS private、soft private、compile-time private
访问内部 class API 和状态(仅编译时隐私)
private
class 的成员是 运行 时间的常规属性。我们可以利用这种灵活性从外部访问 class 内部 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) debugging/logging 情况或 (3) 其他具有项目内部 classes 的高级案例场景(开放式列表)。
对内部变量的访问有点矛盾——否则你一开始就不会private
。举个例子,单元测试应该是 black/grey 框,其中隐藏了私有字段作为实现细节。但在实践中,根据具体情况可能会有有效的方法。
在所有 ES 环境中可用
TS private
修饰符可用于所有 ES 目标。 #
-私有字段仅适用于 target
ES2015
/ES6
或更高版本。在 ES6+ 中,WeakMap
在内部用作下层实现(参见 here)。原生 #
-私有字段目前需要 target
esnext
.
一致性和兼容性
团队可能会使用编码指南和 linter 规则来强制使用 private
作为唯一的访问修饰符。此限制有助于保持一致性并避免以向后兼容的方式与 #
-私有字段表示法混淆。
如果需要,parameter properties (constructor assignment shorthand) are a show stopper. They can only be used with private
keyword and there are no plans 尚未为 #
-私有字段实施它们。
其他原因
private
在某些降级情况下可能会提供更好的 运行 时间性能(参见 here)。- 到目前为止,TS 中没有硬私有 class 方法。
- 有些人更喜欢
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
此外,两者都允许跨实例访问:class A
的一个实例可以访问其他 A
个实例的私有成员:
class A {
private a = 0;
method(arg: A) {
console.log(arg.a); // works
}
}
来源