class 是 TypeScript 里唯一同时占据值空间和类型空间的声明。这解释了为什么 console.log(C.a) 会报错,也解释了 typeof C 和 C 为什么是两回事。
两个身份
1// 类既是值又是类型
2
3class C {
4 a = "123"; // 实例属性
5 static version = "1.0"; // 静态属性
6
7 constructor(public n: number) {}
8
9 method(): string { return this.a; }
10 static create(): C { return new C(0); }
11}
12
13// ---- 值位置:C 是构造函数 ----
14const ctor = C; // ✓ C 是值
15console.log(typeof C); // "function"
16const instance = new C(1);
17
18// ---- 类型位置:C 是实例类型 ----
19let c: C = new C(2); // ✓ C 也是类型
20// 注意:作为类型时,C 表示"实例的类型",不含静态成员
21
22// ---- 关键区别:C 与 typeof C ----
23// C(类型位置)= 实例类型:有 a, n, method
24// typeof C = 构造函数类型:有 version, create, prototype
25
26// 实例类型:能访问实例成员
27const inst: C = new C(3);
28console.log(inst.a, inst.n, inst.method());
29
30// 构造函数类型:能访问静态成员
31const ctorTyped: typeof C = C;
32console.log(ctorTyped.version, ctorTyped.create().n);
33
34// ---- 常见错误:把类当实例用 ----
35// 原归档笔记里的经典问题:
36class D { a = "123" }
37const d: D = new D();
38console.log(d); // D { a: '123' }
39console.log(D); // [class D]
40// @ts-expect-error TS2339: a 是实例属性,不在类(构造函数)上
41console.log(D.a);
42
43// ---- 用 typeof C 表达"传入一个类" ----
44function instantiate<T>(Ctor: new (...args: any[]) => T): T {
45 return new Ctor();
46}
47const made = instantiate(C); // T 推断为 C
48
49// 更精确的构造函数类型
50type Ctor<T> = new (...args: any[]) => T;
51function make<T>(ctor: Ctor<T>): T { return new ctor(); }
52
53// 静态侧 + 实例侧一起约束
54interface WithCreate<T> {
55 create(): T;
56}
57function fromStatic<T, S extends WithCreate<T>>(Static: S): T {
58 return Static.create();
59}
60const fromStaticResult = fromStatic(C);
61
62// ---- 抽象类的类型 ----
63abstract class Base {
64 abstract run(): string;
65 static helper() { return "h"; }
66}
67// typeof Base 包含静态成员
68const baseStatic: typeof Base = Base;
69console.log(baseStatic.helper());
70// @ts-expect-error TS2511: 抽象类不能实例化
71new Base();
72
73// ---- 接口实现类时的类型关系 ----
74interface HasA { a: string }
75// 实例类型满足接口
76const asInterface: HasA = new C(4);
77// 但 typeof C 不满足(构造函数没有 a)
78// @ts-expect-error TS2741: typeof C 缺少 a
79const ctorAsInterface: HasA = C;
80
81console.log(ctor, instance, c, ctorTyped, made, make(C), fromStaticResult, asInterface, ctorAsInterface);
1function
2123 3 123
31.0 0
4D { a: '123' }
1class C {
2 a = "123"; // 实例属性
3 static version = "1.0"; // 静态属性
4 method(): string { }
5 static create(): C { }
6}
7
8const ctor = C; // ✓ 值位置:C 是构造函数
9let c: C = new C(); // ✓ 类型位置:C 是实例类型
| 位置 | C 表示什么 | 有哪些成员 |
|---|---|---|
| 值 | 构造函数(运行时函数) | version、create、prototype |
| 类型 | 实例类型 | a、n、method |
C 与 typeof C
这是理解类双身份的关键:
1const inst: C = new C(); // C 作类型 = 实例类型
2const ctorTyped: typeof C = C; // typeof C = 构造函数类型
typeof C 在类型位置查询 C 这个值的类型,得到构造函数类型——包含静态成员。
1inst.a; // ✓ 实例成员
2inst.method(); // ✓
3
4ctorTyped.version; // ✓ 静态成员
5ctorTyped.create(); // ✓
经典错误:把类当实例用
1class D { a = "123" }
2
3const d: D = new D();
4console.log(d); // D { a: '123' } ← 实例
5console.log(D); // [class D] ← 构造函数
6
7console.log(D.a);
8// error TS2339: Property 'a' does not exist on type 'typeof D'.
a 是实例属性,只在 new D() 出来的对象上。D 本身是构造函数,它上面只有静态成员和 prototype。
报错信息里的
typeof D注意错误说的是 typeof D 而不是 D。因为 D 在值位置被使用时,类型检查器看的是它的值的类型,也就是构造函数类型。看到这个措辞就知道是"静态侧没有这个成员"。
接口只约束实例侧
1interface HasA { a: string }
2
3const asInterface: HasA = new C(4); // ✓ 实例满足
4
5const ctorAsInterface: HasA = C;
6// error TS2741: Property 'a' is missing in type 'typeof C' but required in type
7// 'HasA'.
implements 也是同一规则——它检查实例侧:
1interface Serializable { serialize(): string }
2
3class Foo implements Serializable {
4 serialize() { return ""; } // 必须是实例方法
5}
用 typeof C 表达"传入一个类"
这是实际开发中常见的需求:
1// 泛型构造函数类型
2function instantiate<T>(Ctor: new (...args: any[]) => T): T {
3 return new Ctor();
4}
5
6const made = instantiate(C); // T 推断为 C
更精确的写法:
1type Ctor<T> = new (...args: any[]) => T;
2function make<T>(ctor: Ctor<T>): T { return new ctor(); }
需要静态成员时,用接口约束静态侧:
1interface WithCreate<T> {
2 create(): T;
3}
4
5function fromStatic<T, S extends WithCreate<T>>(Static: S): T {
6 return Static.create();
7}
抽象类
1abstract class Base {
2 abstract run(): string;
3 static helper() { return "h"; }
4}
5
6const baseStatic: typeof Base = Base; // ✓ 静态侧可用
7baseStatic.helper();
8
9new Base();
10// error TS2511: Cannot create an instance of an abstract class.
typeof Base 正常包含静态成员,但实例化被禁止。
为什么这样设计
类的双身份是为了贴合 JavaScript 的实际语义。class 在运行时就是一个构造函数(一个值),而 TypeScript 额外给它一个类型身份来描述实例形状。
对比其他声明:
| 声明 | 值空间 | 类型空间 |
|---|---|---|
class C | ✓ 构造函数 | ✓ 实例类型 |
function f | ✓ 函数 | ✗(用 typeof f) |
interface I | ✗ | ✓ |
type T | ✗ | ✓ |
enum E | ✓ 对象 | ✓ 枚举类型 |
const x | ✓ | ✗(用 typeof x) |
只有 class 和 enum 同时占据两个空间。function 是值,要用 typeof f 才能拿到它的类型。
为什么不能用
instanceof 检查 interfaceinterface 只存在于类型空间,运行时没有对应值,所以 value instanceof SomeInterface 报 TS2693: 'SomeInterface' only refers to a type, but is being used as a value here.。
class 就没这个问题——它在运行时真实存在。详见 类型擦除。
相关
- 类型擦除 —— 为什么 interface 没有运行时对应物
- typeof 的两种含义 —— 类型位置查询的完整规则
- 类与装饰器 —— 类的成员与静态成员