static
#
(私有)get
(获取器)和 set
(设置器)*
(生成器)async
public
、private
或 protected
在本章中,我们将研究类定义在 TypeScript 中的工作原理。
本节是纯 JavaScript 中类定义的备忘单。
class OtherClass {}
class MyClass1 extends OtherClass {
= 1;
publicInstanceField
constructor() {
super();
}
publicPrototypeMethod() {
return 2;
}
}
const inst1 = new MyClass1();
.equal(inst1.publicInstanceField, 1);
assert.equal(inst1.publicPrototypeMethod(), 2); assert
以下部分介绍修饰符
最后,有一个表格显示了如何组合修饰符。
static
class MyClass2 {
static staticPublicField = 1;
static staticPublicMethod() {
return 2;
}
}
.equal(MyClass2.staticPublicField, 1);
assert.equal(MyClass2.staticPublicMethod(), 2); assert
#
(私有)class MyClass3 {
= 1;
#privateField
privateMethod() {
#return 2;
}
static accessPrivateMembers() {
// Private members can only be accessed from inside class definitions
const inst3 = new MyClass3();
.equal(inst3.#privateField, 1);
assert.equal(inst3.#privateMethod(), 2);
assert
}
}.accessPrivateMembers(); MyClass3
JavaScript 警告
TypeScript 从 3.8 版本开始支持私有字段,但目前不支持私有方法。
get
(获取器)和 set
(设置器)粗略地说,访问器是通过访问属性调用的方法。访问器有两种:获取器和设置器。
class MyClass5 {
= 'Rumpelstiltskin';
#name
/** Prototype getter */
name() {
get return this.#name;
}
/** Prototype setter */
name(value) {
set this.#name = value;
}
}const inst5 = new MyClass5();
.equal(inst5.name, 'Rumpelstiltskin'); // getter
assert.name = 'Queen'; // setter
inst5.equal(inst5.name, 'Queen'); // getter assert
*
(生成器)class MyClass6 {
* publicPrototypeGeneratorMethod() {
yield 'hello';
yield 'world';
}
}
const inst6 = new MyClass6();
.deepEqual(
assert...inst6.publicPrototypeGeneratorMethod()],
['hello', 'world']); [
async
class MyClass7 {
async publicPrototypeAsyncMethod() {
const result = await Promise.resolve('abc');
return result + result;
}
}
const inst7 = new MyClass7();
.publicPrototypeAsyncMethod()
inst7.then(result => assert.equal(result, 'abcabc'));
const publicInstanceFieldKey = Symbol('publicInstanceFieldKey');
const publicPrototypeMethodKey = Symbol('publicPrototypeMethodKey');
class MyClass8 {
= 1;
[publicInstanceFieldKey]
[publicPrototypeMethodKey]() {return 2;
}
}
const inst8 = new MyClass8();
.equal(inst8[publicInstanceFieldKey], 1);
assert.equal(inst8[publicPrototypeMethodKey](), 2); assert
注释
Symbol.iterator
。但是,方括号内可以使用任何表达式。字段(无级别表示构造存在于实例级别)
级别 | 可见性 |
---|---|
(实例) | |
(实例) | # |
静态 |
|
静态 |
# |
方法(无级别表示构造存在于原型级别)
级别 | 访问器 | 异步 | 生成器 | 可见性 |
---|---|---|---|---|
(原型) | ||||
(原型) | 获取 |
|||
(原型) | 设置 |
|||
(原型) | 异步 |
|||
(原型) | * |
|||
(原型) | 异步 |
* |
||
(与原型关联) | # |
|||
(与原型关联) | 获取 |
# |
||
(与原型关联) | 设置 |
# |
||
(与原型关联) | 异步 |
# |
||
(与原型关联) | * |
# |
||
(与原型关联) | 异步 |
* |
# |
|
静态 |
||||
静态 |
获取 |
|||
静态 |
设置 |
|||
静态 |
异步 |
|||
静态 |
* |
|||
静态 |
异步 |
* |
||
静态 |
# |
|||
静态 |
获取 |
# |
||
静态 |
设置 |
# |
||
静态 |
异步 |
# |
||
静态 |
* |
# |
||
静态 |
异步 |
* |
# |
方法的限制
重要的是要记住,对于类,有两条原型对象链。
请考虑以下纯 JavaScript 示例。
class ClassA {
static staticMthdA() {}
constructor(instPropA) {
this.instPropA = instPropA;
}prototypeMthdA() {}
}class ClassB extends ClassA {
static staticMthdB() {}
constructor(instPropA, instPropB) {
super(instPropA);
this.instPropB = instPropB;
}prototypeMthdB() {}
}const instB = new ClassB(0, 1);
图 1 显示了由 ClassA
和 ClassB
创建的原型链的外观。
默认情况下,TypeScript 中的所有数据槽都是公共属性。有两种方法可以保持数据的私密性。
接下来我们将分别介绍这两种方法。
请注意,TypeScript 目前不支持私有方法。
私有属性是仅限 TypeScript(静态)的特性。任何属性都可以通过在其前面加上关键字 private
来设为私有(A 行)。
class PersonPrivateProperty {: string; // (A)
private nameconstructor(name: string) {
.name = name;
this
}sayHello() {
return `Hello ${this.name}!`;
} }
如果我们在错误的作用域中访问该属性,现在会收到编译时错误(A 行)。
= new PersonPrivateProperty('John');
const john
.equal(
assert.sayHello(), 'Hello John!');
john
// @ts-expect-error: Property 'name' is private and only accessible
// within class 'PersonPrivateProperty'. (2341)
.name; // (A) john
但是,private
在运行时不会改变任何内容。在那里,属性 .name
与公共属性没有区别。
.deepEqual(
assert.keys(john),
Object'name']); [
当我们查看类编译成的 JavaScript 代码时,我们也可以看到私有属性在运行时不受保护。
class PersonPrivateProperty {
constructor(name) {
this.name = name;
}sayHello() {
return `Hello ${this.name}!`;
} }
私有字段是 TypeScript 从 3.8 版本开始支持的一项新的 JavaScript 特性。
class PersonPrivateField {: string;
#nameconstructor(name: string) {
.#name = name;
this
}sayHello() {
return `Hello ${this.#name}!`;
} }
此版本的 Person
的使用方式与私有属性版本基本相同。
= new PersonPrivateField('John');
const john
.equal(
assert.sayHello(), 'Hello John!'); john
但是,这一次,数据是完全封装的。在类外部使用私有字段语法甚至是一个 JavaScript 语法错误。这就是为什么我们必须在 A 行中使用 eval()
,以便我们可以执行此代码。
.throws(
assert=> eval('john.#name'), // (A)
()
{: 'SyntaxError',
name: "Private field '#name' must be declared in "
message+ "an enclosing class",
;
})
.deepEqual(
assert.keys(john),
Object; [])
编译结果现在要复杂得多(略有简化)。
var __classPrivateFieldSet = function (receiver, privateMap, value) {
if (!privateMap.has(receiver)) {
throw new TypeError(
'attempted to set private field on non-instance');
}.set(receiver, value);
privateMapreturn value;
;
}
// Omitted: __classPrivateFieldGet
var _name = new WeakMap();
class Person {
constructor(name) {
// Add an entry for this instance to _name
.set(this, void 0);
_name
// Now we can use the helper function:
__classPrivateFieldSet(this, _name, name);
}// ···
}
此代码使用了一种常见的技术来保持实例数据的私密性。
有关此主题的更多信息,请参阅“面向急于求成的程序员的 JavaScript”。
私有字段和私有属性不能在子类中访问(A 行)。
class PrivatePerson {: string;
private nameconstructor(name: string) {
.name = name;
this
}sayHello() {
return `Hello ${this.name}!`;
}
}
class PrivateEmployee extends PrivatePerson {: string;
private companyconstructor(name: string, company: string) {
super(name);
.company = company;
this
}sayHello() {
// @ts-expect-error: Property 'name' is private and only
// accessible within class 'PrivatePerson'. (2341)
return `Hello ${this.name} from ${this.company}!`; // (A)
} }
我们可以通过在 A 行中将 private
更改为 protected
来修复前面的示例(为了保持一致性,我们也在 B 行中进行了更改)。
class ProtectedPerson {: string; // (A)
protected nameconstructor(name: string) {
.name = name;
this
}sayHello() {
return `Hello ${this.name}!`;
}
}
class ProtectedEmployee extends ProtectedPerson {: string; // (B)
protected companyconstructor(name: string, company: string) {
super(name);
.company = company;
this
}sayHello() {
return `Hello ${this.name} from ${this.company}!`; // OK
} }
构造函数也可以是私有的。当我们有静态工厂方法并希望客户端始终使用这些方法,而从不直接使用构造函数时,这很有用。静态方法可以访问私有类成员,这就是为什么工厂方法仍然可以使用构造函数的原因。
在以下代码中,有一个静态工厂方法 DataContainer.create()
。它通过异步加载的数据设置实例。在工厂方法中保留异步代码可以使实际类完全同步。
class DataContainer {: string;
#datacreate() {
static async = await Promise.resolve('downloaded'); // (A)
const data new this(data);
return
}constructor(data: string) {
private .#data = data;
this
}getData() {
'DATA: '+this.#data;
return
}
}.create()
DataContainer.then(dc => assert.equal(
.getData(), 'DATA: downloaded')); dc
在实际代码中,我们将使用 fetch()
或类似的基于 Promise 的 API 在 A 行中异步加载数据。
私有构造函数阻止 DataContainer
被子类化。如果我们想允许子类,我们必须将其设为 protected
。
如果打开了编译器设置 --strictPropertyInitialization
(如果我们使用 --strict
,则默认打开),则 TypeScript 会检查是否正确初始化了所有声明的实例属性。
通过构造函数中的赋值。
class Point {: number;
x: number;
yconstructor(x: number, y: number) {
.x = x;
this.y = y;
this
} }
或者通过属性声明的初始化器。
class Point {= 0;
x = 0;
y
// No constructor needed
}
但是,有时我们以 TypeScript 无法识别的方式初始化属性。然后,我们可以使用感叹号(*确定赋值断言*)来关闭 TypeScript 的警告(A 行和 B 行)。
class Point {!: number; // (A)
x!: number; // (B)
yconstructor() {
.initProperties();
this
}initProperties() {
.x = 0;
this.y = 0;
this
} }
在以下示例中,我们还需要确定赋值断言。在这里,我们通过构造函数参数 props
设置实例属性。
// (A)
class CompilerError implements CompilerErrorProps { !: number;
line!: string;
descriptionconstructor(props: CompilerErrorProps) {
.assign(this, props); // (B)
Object
}
}
// Helper interface for the parameter properties
interface CompilerErrorProps {: number,
line: string,
description
}
// Using the class:
= new CompilerError({
const err : 123,
line: 'Unexpected token',
description; })
注意
Object.assign()
将参数 props
的属性复制到 this
中。implements
确保类声明了接口 CompilerErrorProps
中的所有属性。public
、private
或 protected
如果我们对构造函数参数使用关键字 public
,则 TypeScript 会为我们做两件事。
因此,以下两个类是等效的。
class Point1 {constructor(public x: number, public y: number) {
}
}
class Point2 {: number;
x: number;
yconstructor(x: number, y: number) {
.x = x;
this.y = y;
this
} }
如果我们使用 private
或 protected
而不是 public
,则相应的实例属性是私有的或受保护的(而不是公共的)。
在 TypeScript 中,两种构造可以是抽象的。
以下代码演示了抽象类和方法。
一方面,有抽象超类 Printable
及其辅助类 StringBuilder
。
class StringBuilder {= '';
string add(str: string) {
.string += str;
this
}
}abstract class Printable {
toString() {
= new StringBuilder();
const out .print(out);
this.string;
return out
}abstract print(out: StringBuilder): void;
}
另一方面,有具体子类 Entries
和 Entry
。
class Entries extends Printable {: Entry[];
entriesconstructor(entries: Entry[]) {
super();
.entries = entries;
this
}print(out: StringBuilder): void {
for (const entry of this.entries) {
.print(out);
entry
}
}
}
class Entry extends Printable {: string;
key: string;
valueconstructor(key: string, value: string) {
super();
.key = key;
this.value = value;
this
}print(out: StringBuilder): void {
.add(this.key);
out.add(': ');
out.add(this.value);
out.add('\n');
out
} }
最后,这是我们使用 Entries
和 Entry
的方式。
= new Entries([
const entries new Entry('accept-ranges', 'bytes'),
new Entry('content-length', '6518'),
;
]).equal(
assert.toString(),
entries'accept-ranges: bytes\ncontent-length: 6518\n');
关于抽象类的注释