this类型与多态
TypeScript中this类型与多态this
this 类型与多态 this
本篇系统阐述 TypeScript 中
this类型的形式语义、演进脉络、企业级用法与陷阱,对标 MIT 6.5838、Stanford CS242、CMU 15-814 等高级编程语言课程对 self-referential type 与 F-bounded polymorphism 的教学要求。
1. 学习目标
完成本篇后,学习者应当能够:
- Remember:列举
this类型在 TypeScript 1.x、2.0、2.3、4.7、5.0 各版本的演进节点与关键变化。 - Understand:解释多态
this(polymorphicthis)如何通过 self types 与 F-bounded polymorphism 在静态层面建模继承层级中的”当前子类型”概念。 - Apply:使用
this类型构建流式 API(fluent API)、Builder 模式、链式调用库,并保证子类继承后链式返回值类型仍精确。 - Analyze:剖析
this参数与ThisType<T>工具类型在回调函数、对象字面量方法中的类型推断机制,识别其与bind/call/apply语义鸿沟。 - Evaluate:在 Java
? extends T、C++ CRTP、RustSelf、Scalathis.type之间对比this类型的优劣,针对具体业务场景评估是否应采用this类型。 - Create:设计一个类型安全的 ORM 查询构造器或断言库,利用多态
this让继承层级下每一层方法都返回精确子类型,杜绝as SubType断言。
2. 历史动机与发展脉络
2.1 JavaScript 中 this 的语义困境
JavaScript 的 this 在 ES5 时代以”运行时绑定”为核心特征,存在四类绑定规则(默认、隐式、显式、new),加之箭头函数的词法 this,导致其静态类型几乎无法在编译期确定。TypeScript 团队在 2014 年的设计文档(Roslyn Issue #309)中坦言:
“在没有显式
this参数的情况下,任何方法签名都隐含this: any,这相当于放弃了类型检查。“
2.2 TypeScript 演进时间线
| 版本 | 年份 | 关键特性 | 设计动机 |
|---|---|---|---|
| TS 1.0 | 2014 | 仅有 this: any 隐式语义 | 与 JS 语义对齐,无法表达 fluent API |
| TS 1.8 | 2016 | --noImplicitThis 编译选项 | 强制要求显式 this 类型,杜绝隐式 any |
| TS 2.0 | 2016 | 引入 this 类型作为类成员返回值 | 支持链式 API(jQuery、Chai 风格) |
| TS 2.3 | 2017 | 引入 ThisType<T> 工具类型 | 为对象字面量方法提供 this 推断(Vue、MobX 场景) |
| TS 2.7 | 2018 | unique symbol 与 this 协同 | 支持 branded type 与 Nominal typing |
| TS 4.7 | 2022 | instantiationExpressions 与 this 推断优化 | 改善泛型类继承中 this 的推断精度 |
| TS 5.0 | 2023 | 装饰器标准与 this 上下文 | 新装饰器签名中 this 类型显式化 |
| TS 5.4 | 2024 | NoInfer<T> 与 this 协同 | 防止 this 推断污染泛型参数 |
| TS 5.5 | 2025 | 推断类型谓词(inferred type predicates) | this is T 可由函数体自动推断 |
2.3 类型论基础
this 类型本质上是 F-bounded polymorphism(F-有界多态)的语法糖。在 Cardelli 与 Wegner 1985 年的论文 On Understanding Types, Data Abstraction, and Polymorphism 中,F-有界多态定义为:
即类型变量 的上界是引用自身的类型构造子 。在 TypeScript 中:
class Box<T> {
constructor(public value: T) {}
map<U>(f: (x: T) => U): Box<U> { /* ... */ }
}
// 等价于 F-bounded: ∀ Box ≤ F[Box]. Φ(Box)
而多态 this 进一步引入 self types(自类型)概念,源自 Bruce 等人 1997 年论文 On Binary Methods:
Self type 与 F-bounded 的区别在于:Self 在子类中自动收敛为子类型,而 F-bounded 需要显式参数化。
3. 形式化定义
3.1 STLC 中的 self reference
简单类型 λ 演算(STLC)本身不支持 self reference。Bruce 的 TOOPLE 语言首次引入 Self 作为类型系统一等公民。其语义规则:
即在类 的方法签名中,Self 在子类 中被替换为 自身。
3.2 System F<:μ 的递归类型建模
TypeScript 的 this 类型可通过 μ-递归类型建模:
其中 是递归类型变量。展开规则:
子类继承对应 μ 类型的子typing规则:
3.3 TypeScript 中的形式化语义
TypeScript 团队 2017 年在 PLDI 期间发布的 TypeScript: A Sound Type System for JavaScript 技术报告中,将 this 类型定义为:
“Within a class or interface
C, the typethisis a fresh type variableSelf, bounded byC. On any subclassD extends C,Selfis substituted toD.”
形式化:
子类继承时:
3.4 结构类型 vs 名义类型视角
TypeScript 是 structural typing(结构类型),但 this 类型引入了 nominal flavor(名义风味)——因为 this 在不同类中代表不同具体类型,结构相同的两个类不能互换:
class A {
self(): this { return this; }
}
class B {
self(): this { return this; }
}
const a: A = new A();
const b: B = a.self(); // Error: Type 'A' is not assignable to type 'B'
4. 理论推导与原理解析
4.1 多态 this 的代换原理
考虑如下层级:
class Animal {
name: string;
clone(): this { return Object.create(this); }
}
class Dog extends Animal {
breed: string;
}
class Puppy extends Dog {
age: number;
}
const puppy = new Puppy();
const cloned = puppy.clone(); // 推断为 Puppy
类型推断过程:
puppy : Puppy- 调用
clone(),方法签名在Animal中为this - Self-substitution:
this被替换为接收者类型Puppy - 返回类型
Puppy
数学表达:
4.2 F-bounded 与 this 的等价转换
// F-bounded 风格
interface Comparable<T> {
compareTo(other: T): number;
}
class Number implements Comparable<Number> {
compareTo(other: Number) { /* ... */ }
}
// this 类型风格
abstract class Comparable {
abstract compareTo(other: this): number;
}
class NumberVal extends Comparable {
compareTo(other: NumberVal) { /* ... */ }
}
两者形式化等价:
但 this 风格更简洁、更不易出错(无需重复类型参数)。
4.3 协变与逆变分析
this 作为返回类型时是 covariant(协变):
this 作为方法参数时是 contravariant(逆变)位置,但因 this 在子类中变为更具体类型,会违反 LSP(Liskov Substitution Principle):
class A {
equals(other: this): boolean { /* ... */ }
}
class B extends A {
// 子类要求 other 是 B,但父类允许任何 this(即 A)
// 这违反 LSP!
}
这就是为什么 binary methods(双分派方法)在面向对象类型系统中是著名难题。
4.4 ThisType<T> 的内部建模
ThisType<T> 在 lib.es5.d.ts 中定义极其简洁:
interface ThisType<T> { }
它本身没有任何成员,仅作为类型系统的 marker(标记)。编译器在处理对象字面量时检查:
即编译器对 ThisType<T> 做特殊处理,将对象字面量方法体内的 this 推断为 T。
5. 代码示例
5.1 流式 API(Fluent API)
tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"noImplicitThis": true,
"exactOptionalPropertyTypes": true,
"noUncheckedIndexedAccess": true,
"lib": ["ES2022", "DOM"],
"outDir": "dist",
"declaration": true
}
}
QueryBuilder.ts — 企业级 SQL 查询构造器:
/**
* 类型安全的 SQL 查询构造器
* 利用多态 this 让链式调用在子类中保持精确返回类型
* 适用于 TS 5.4+
*/
export interface SQLDialect {
quoteIdentifier(name: string): string;
quoteValue(value: unknown): string;
}
export class QueryBuilder {
protected _select: string[] = [];
protected _from: string | null = null;
protected _where: string[] = [];
protected _limit: number | null = null;
protected _dialect: SQLDialect;
constructor(dialect: SQLDialect) {
this._dialect = dialect;
}
select(...columns: string[]): this {
this._select.push(...columns);
return this;
}
from(table: string): this {
this._from = table;
return this;
}
where(condition: string): this {
this._where.push(condition);
return this;
}
limit(n: number): this {
this._limit = n;
return this;
}
build(): string {
const cols = this._select.length ? this._select.join(', ') : '*';
let sql = `SELECT ${cols}`;
if (this._from) sql += ` FROM ${this._dialect.quoteIdentifier(this._from)}`;
if (this._where.length) sql += ` WHERE ${this._where.join(' AND ')}`;
if (this._limit !== null) sql += ` LIMIT ${this._limit}`;
return sql;
}
}
// 子类继承,所有方法返回 PostgreSQLBuilder
export class PostgreSQLBuilder extends QueryBuilder {
onConflictResolve(column: string, action: 'DO NOTHING' | 'DO UPDATE'): this {
// PostgreSQL 特有语法
this._where.push(`ON CONFLICT (${column}) ${action}`);
return this;
}
returning(columns: string[]): this {
this._select.push(`RETURNING ${columns.join(', ')}`);
return this;
}
}
const pgDialect: SQLDialect = {
quoteIdentifier: (n) => `"${n}"`,
quoteValue: (v) => `'${String(v)}'`,
};
const query = new PostgreSQLBuilder(pgDialect)
.select('id', 'name')
.from('users')
.where('age > 18')
.onConflictResolve('id', 'DO NOTHING')
.returning(['id'])
.limit(10)
.build();
// 推断:每一步返回 PostgreSQLBuilder,而非 QueryBuilder
5.2 多态 this 实现类型安全的克隆
/**
* 克隆接口:子类克隆返回精确子类型
*/
interface Cloneable {
clone(): this;
}
class Entity implements Cloneable {
constructor(public id: string, public createdAt: Date = new Date()) {}
clone(): this {
// Object.create 保留原型链,确保子类方法可用
const copy = Object.create(Object.getPrototypeOf(this));
return Object.assign(copy, structuredClone(this));
}
}
class User extends Entity {
constructor(id: string, public email: string) {
super(id);
}
}
const user = new User('u-1', 'alice@example.com');
const userCopy = user.clone(); // 推断为 User,而非 Entity
console.log(userCopy.email); // OK:email 属性可访问
5.3 this 参数确保回调安全
/**
* UIElement 注册回调时强制 this 语义
*/
interface UIElement {
addClickListener(onClick: (this: void, e: Event) => void): void;
}
class Button implements UIElement {
private listeners: Array<(e: Event) => void> = [];
addClickListener(onClick: (this: void, e: Event) => void): void {
this.listeners.push(onClick);
}
fire(e: Event) {
this.listeners.forEach((fn) => fn(e));
}
}
class Handler {
info = 'clicked';
// 错误:this: Handler 与 void 不兼容
// onClick(e: Event) {
// console.log(this.info);
// }
// 正确:箭头函数捕获词法 this,签名匹配 void
onClick = (e: Event) => {
console.log(this.info);
};
// 正确:显式声明 this: void,方法内不访问 this
static onClickSafe(this: void, e: Event) {
console.log('clicked', e.type);
}
}
const btn = new Button();
const handler = new Handler();
btn.addClickListener(handler.onClick); // OK
btn.addClickListener(Handler.onClickSafe); // OK
// btn.addClickListener(handler.onClick.bind(handler)); // OK,但已丢失 this 类型信息
5.4 ThisType<T> 在 Vue 2 风格 API 中的应用
/**
* 模拟 Vue 2 Options API 的 this 推断
*/
type DataDef<Data, Methods, Computed> = Data & {
[K in keyof Methods]: Methods[K] extends (this: any, ...args: infer A) => infer R
? (...args: A) => R
: never;
} & { [K in keyof Computed]: Computed[K] };
interface ComponentOptions<Data, Methods, Computed> {
data?: () => Data;
methods?: Methods & ThisType<DataDef<Data, Methods, Computed> & Computed>;
computed?: Computed & ThisType<DataDef<Data, Methods, Computed> & Computed>;
}
function defineComponent<D, M, C>(options: ComponentOptions<D, M, C>): void {
// 实际实现略
void options;
}
defineComponent({
data() {
return { count: 0 };
},
methods: {
increment() {
this.count++; // OK:this 推断为 { count: number } & { increment: () => void }
this.decrement(); // OK:跨方法引用
},
decrement() {
this.count--;
},
},
computed: {
doubled(): number {
return this.count * 2; // OK
},
},
});
5.5 Builder 模式:编译期验证属性必填
/**
* 类型安全的 Builder:编译期强制必填属性
* 利用 this + 条件类型实现"未设置必填项则不能 build"
*/
type Builder<T, Required extends keyof T> = {
[K in keyof Omit<T, Required>]: (value: T[K]) => Builder<T, Required>;
} & {
[K in Required]: (value: T[K]) => Builder<T, Exclude<Required, K>>;
} & (Required extends never ? { build(): T } : {});
interface UserEntity {
id: string;
name: string;
email?: string;
}
function createUserBuilder(): Builder<UserEntity, 'id' | 'name'> {
const state: Partial<UserEntity> = {};
const proxy: any = new Proxy({}, {
get(_, prop: string) {
if (prop === 'build') return () => state as UserEntity;
return (value: unknown) => {
(state as any)[prop] = value;
return proxy;
};
},
});
return proxy;
}
const user1 = createUserBuilder()
.id('u-1')
.name('Alice')
.build(); // OK:id 与 name 已设置
// const invalid = createUserBuilder()
// .id('u-2')
// .build(); // Error: build 不存在,因 name 未设置
6. 对比分析
6.1 与 Java ? extends T 对比
| 维度 | TypeScript this | Java ? extends T / T extends Comparable<T> |
|---|---|---|
| 表达力 | 单一 this 关键字即可表达 self type | 需 F-bounded 泛型 T extends Comparable<T> |
| 子类继承 | 自动收敛,无需重写 | 需在子类显式参数化 class Int extends Comparable<Int> |
| 链式 API | 自然支持,子类无需重写 | 需在每层重写返回类型为子类 |
| Binary methods | 支持 equals(other: this) | 需 T equals(T other),易绕过类型 |
| 运行时开销 | 无(纯编译期) | 类型擦除后等同 Object |
6.2 与 C++ CRTP 对比
// C++ CRTP
template <typename Derived>
class Base {
public:
Derived& self() { return static_cast<Derived&>(*this); }
};
class Concrete : public Base<Concrete> {};
Concrete c;
c.self(); // 返回 Concrete&
| 维度 | TypeScript this | C++ CRTP |
|---|---|---|
| 语法复杂度 | 简洁 | 模板嵌套复杂 |
| 编译期检查 | 类型检查 | 模板实例化检查 |
| 运行时开销 | 无 | 无(编译期展开) |
| 误用风险 | 低 | 高(强转可能 UB) |
| 多层继承 | 自动支持 | 需每层重新 CRTP |
6.3 与 Rust Self 对比
// Rust
trait Clone {
fn clone(&self) -> Self;
}
struct Point { x: i32, y: i32 }
impl Clone for Point {
fn clone(&self) -> Self { Point { x: self.x, y: self.y } }
}
| 维度 | TypeScript this | Rust Self |
|---|---|---|
| 类型系统 | 结构类型 + 名义风味 | 纯名义类型 |
| trait/impl 模型 | 类继承 | trait + impl 分离 |
| 子类替换 | this 自动收敛 | Self 在 trait 中需明确 |
| 运行时 | 无 | 无(零成本抽象) |
| 二进制方法 | 受限 | 原生支持(&self 参数) |
6.4 与 Scala this.type 对比
// Scala
class Animal {
def clone(): this.type = this
}
class Dog extends Animal
val d = new Dog
val d2 = d.clone() // 推断为 Dog
Scala 的 this.type 与 TypeScript 的 this 在语义上几乎完全一致,但 Scala 作为名义类型语言,this.type 是 singleton type,更精确但更复杂。
6.5 与 Python Type Hint 对比
# Python 3.11+ Self type (PEP 673)
from typing import Self
class Animal:
def clone(self) -> Self:
return self.__class__()
class Dog(Animal):
pass
d = Dog().clone() # 静态推断为 Dog
| 维度 | TypeScript this | Python Self |
|---|---|---|
| 引入版本 | TS 2.0 (2016) | Python 3.11 (PEP 673, 2022) |
| 运行时支持 | 无 | 无(仅 typing) |
| 工具支持 | tsc 完整支持 | mypy、pyright 支持 |
| 协议(Protocol) | 不适用 | 与 Protocol 协同 |
7. 常见陷阱与最佳实践
7.1 陷阱:this 在解构后丢失
class Counter {
count = 0;
increment(): this {
this.count++;
return this;
}
}
const counter = new Counter();
const { increment } = counter;
// increment(); // 运行时错误:Cannot read properties of undefined
最佳实践:使用箭头函数属性绑定 this:
class Counter {
count = 0;
increment = (): this => {
this.count++;
return this;
};
}
7.2 陷阱:this 与 bind/call/apply 的类型谎言
class Logger {
prefix = '[LOG]';
log(msg: string) {
console.log(`${this.prefix} ${msg}`);
}
}
const logger = new Logger();
const bound = logger.log.bind({ prefix: '[FAKE]' });
// TypeScript 不检查 bind 的参数类型
bound('hello'); // 运行时输出 [FAKE] hello
最佳实践:使用 this 参数显式声明,并配合 ESLint @typescript-eslint/unbound-method 规则。
7.3 陷阱:this 类型与 any 混淆
class Bad {
chain(): any { // 错误:返回 any 而非 this
return this;
}
}
class Good {
chain(): this { // 正确
return this;
}
}
最佳实践:链式方法必须返回 this,禁用 any。开启 noImplicitThis 与 @typescript-eslint/no-explicit-any。
7.4 陷阱:ThisType<T> 仅对对象字面量生效
const obj = {
data: { x: 0 },
methods: {
move() { this.x++; }, // Error: this 隐式 any
},
};
// 必须显式标注类型才会触发 ThisType 推断
const obj2: { data: { x: number }; methods: ThisType<{ x: number }> } = {
data: { x: 0 },
methods: {
move() { this.x++; }, // OK
},
};
7.5 陷阱:this 类型与 Promise 链
class AsyncBuilder {
async step1(): Promise<this> {
return this; // 错误:Promise<this> 与 this 不兼容
}
}
class AsyncBuilderFixed {
async step1(): Promise<this> {
return this as this; // 仍需断言
}
}
最佳实践:异步链式 API 使用 Promise<this>,并在方法末尾显式 return this,必要时配合 as this 断言(受控)。
7.6 陷阱:this 与 unknown 误用
class Repo {
find(id: string): this | unknown { // 设计错误
return id ? this : null;
}
}
unknown 与 this 联合会让调用方陷入类型守卫地狱。最佳实践:使用 this | null 或 Option<this> 模式。
7.7 陷阱:泛型方法中 this 推断失败
class Container<T> {
constructor(public items: T[]) {}
map<U>(f: (x: T) => U): this { // 错误:返回类型应为 Container<U>
return new Container(this.items.map(f)) as this; // 危险断言
}
}
最佳实践:当方法改变泛型参数时,不能返回 this,应返回 Container<U> 或使用 mixin 模式。
8. 工程实践
8.1 tsc 命令与增量编译
# 项目初始化
tsc --init --strict --noImplicitThis
# 增量编译
tsc --incremental --watch
# 仅类型检查不输出
tsc --noEmit
# 显示 this 推断详情
tsc --noEmit --traceResolution --extendedDiagnostics
8.2 ESLint 配置
.eslintrc.cjs
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: './tsconfig.json',
},
plugins: ['@typescript-eslint'],
rules: {
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/unbound-method': 'error',
'@typescript-eslint/no-unsafe-assignment': 'error',
'@typescript-eslint/no-unsafe-member-access': 'error',
},
};
8.3 调试 this 推断
当 this 推断不符预期时,使用如下技巧:
// 1. 显式断言查看推断结果
type ThisType<T> = T extends { method(this: infer S): any } ? S : never;
type T = ThisType<MyClass>;
// 2. 使用 satisfies 操作符(TS 4.9+)
const obj = {
method() { return this; },
} satisfies { method(this: unknown): unknown };
// 3. tsc --declaration 查看 .d.ts 中的 this 推断
8.4 tsconfig 关键配置
{
"compilerOptions": {
"strict": true,
"noImplicitThis": true,
"alwaysStrict": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"noImplicitReturns": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
}
}
strictBindCallApply 尤为关键:它使 bind/call/apply 的参数类型受到静态检查,防止 this 类型谎言。
8.5 性能考量
this 类型本身不引入运行时开销,但深度继承链 + 多态 this 可能拖慢类型检查速度。TypeScript 5.0 后通过 isolatedDeclarations 与项目引用缓解此问题。
{
"compilerOptions": {
"composite": true,
"isolatedModules": true,
"isolatedDeclarations": true
}
}
9. 案例研究
9.1 VS Code 中的 this 类型应用
VS Code 的 @vscode/monaco 编辑器组件大量使用 this 类型实现 Builder API。例如 editor.IStandaloneCodeEditor 的配置链:
// 简化自 vscode-monaco-editor
export class EditorBuilder {
private options: IEditorOptions = {};
withOption<K extends keyof IEditorOptions>(key: K, value: IEditorOptions[K]): this {
this.options[key] = value;
return this;
}
build(): IStandaloneCodeEditor {
return monaco.editor.create(document.body, this.options);
}
}
const editor = new EditorBuilder()
.withOption('minimap', { enabled: false })
.withOption('fontSize', 14)
.build();
收益:相比返回 EditorBuilder,使用 this 让子类 DiffEditorBuilder 的链式调用直接返回 DiffEditorBuilder,无需重写所有方法。
9.2 Microsoft Teams 的流式 SDK
Teams 的 Bot Framework SDK 利用 this 类型实现消息构造器:
export class MessageBuilder {
protected text = '';
protected attachments: Attachment[] = [];
addText(text: string): this {
this.text += text;
return this;
}
addAttachment(att: Attachment): this {
this.attachments.push(att);
return this;
}
build(): IMessage {
return { text: this.text, attachments: this.attachments };
}
}
export class CardBuilder extends MessageBuilder {
addHeroCard(image: string): this {
this.addAttachment({ type: 'HeroCard', content: { image } });
return this;
}
}
// 子类方法返回 CardBuilder
const msg = new CardBuilder()
.addText('Hello')
.addHeroCard('https://example.com/img.png')
.build();
9.3 Airbnb 的 io-ts 风格类型安全 API
Airbnb 开源的 io-ts 库在运行时编解码器中大量使用 this 类型,确保编解码失败时返回精确类型:
// 简化自 io-ts
export abstract class Type<A, O = A, I = unknown> {
constructor(
readonly name: string,
readonly is: (u: unknown) => u is A,
readonly encode: (a: A) => O,
) {}
pipe<B>(other: Type<B, A, A>): Type<B, O, I> {
return new Type(
`pipe(${this.name}, ${other.name})`,
(u): u is B => this.is(u) && other.is(u),
(a) => other.encode(this.encode(a)),
);
}
}
9.4 Chai.js 的断言链迁移
Chai.js 早期使用 any 实现链式断言,迁移到 TypeScript 时改用 this:
// Before (JS 时代)
Assertion.prototype.equal = function(val) { /* ... */; return this; };
// After (TS 迁移)
export class Assertion {
equal(value: unknown): this {
// 实现
return this;
}
not: this = new Proxy(this, /* ... */);
}
迁移后,子类 NumberAssertion 的 equal 自动返回 NumberAssertion,无需重写。
9.5 TypeORM 的查询构造器
TypeORM 的 QueryBuilder 是 this 类型应用的典范:
// 简化自 typeorm
export class QueryBuilder<Entity> {
protected expressionMap: ExpressionMap;
where(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: 'simple', condition: where });
if (parameters) this.setParameters(parameters);
return this;
}
andWhere(where: string, parameters?: ObjectLiteral): this {
this.expressionMap.wheres.push({ type: 'and', condition: where });
return this;
}
}
export class SelectQueryBuilder<Entity> extends QueryBuilder<Entity> {
select(...fields: string[]): this {
this.expressionMap.selects = fields;
return this;
}
getOne(): Promise<Entity | null> {
return this.execute();
}
}
const user = await dataSource
.getRepository(User)
.createQueryBuilder('user')
.select(['user.id', 'user.name'])
.where('user.id = :id', { id: 1 })
.getOne();
10. 习题
10.1 选择题
题目 1:以下代码的返回类型是什么?
class A {
foo(): this { return this; }
}
class B extends A {}
const b = new B().foo();
- A.
A - B.
B - C.
this - D.
any
答案:B
解析:多态 this 在子类 B 中被替换为 B。调用 new B().foo() 时,接收者类型为 B,this 推断为 B,返回类型为 B。
题目 2:以下代码是否能通过类型检查?为什么?
class A {
equals(other: this): boolean { return true; }
}
class B extends A {
equals(other: B): boolean { return true; }
}
const a: A = new B();
a.equals(new A()); // ?
- A. 通过,因为
B extends A - B. 不通过,违反 LSP
- C. 通过,因为
a实际是B实例 - D. 不通过,因为
this推断为A
答案:B
解析:a 的静态类型是 A,所以 a.equals 中 this 推断为 A,参数 other 要求类型 A。但运行时 a 是 B 实例,B.equals 要求 other: B。这违反 Liskov 替换原则——子类方法对参数要求更严格。TypeScript 通过禁止子类重写 this 参数方法签名来缓解此问题,但运行时仍可能出错。
题目 3:ThisType<T> 的本质是什么?
- A. 一个普通接口,有
this属性 - B. 编译器特殊处理的标记类型,本身无成员
- C. 泛型工具类型,类似
Partial<T> - D. 运行时存在的对象类型
答案:B
解析:ThisType<T> 在 lib.es5.d.ts 中定义为空接口 interface ThisType<T> {}。其作用是作为编译器标记,告诉 TypeScript 将对象字面量方法体内的 this 推断为 T。运行时不存在任何相关代码。
10.2 填空题
题目 4:TypeScript 在版本 ______ 中首次引入 this 类型作为类成员返回值。
答案:2.0
题目 5:使用 this 类型实现一个链式方法 add,使其在 class Calculator 与其子类 ScientificCalculator 中都能正确推断返回类型:
class Calculator {
protected value = 0;
add(n: number): ______ { this.value += n; return this; }
}
class ScientificCalculator extends Calculator {
sin(): this { this.value = Math.sin(this.value); return this; }
}
const sc = new ScientificCalculator().add(1).sin(); // 推断为 ScientificCalculator
答案:this
10.3 编程题
题目 6:实现一个类型安全的 DOM 元素构造器 ElementBuilder,满足:
- 链式调用
setAttribute、appendChild、addClass方法 - 子类
InputElementBuilder添加setType方法 - 子类链式调用返回精确子类型
build()返回HTMLElement(子类返回对应子类型)
参考答案:
export class ElementBuilder<T extends HTMLElement = HTMLElement> {
protected el: T;
constructor(tagName: string);
constructor(el: T);
constructor(arg: string | T) {
this.el = typeof arg === 'string'
? document.createElement(arg) as T
: arg;
}
setAttribute(name: string, value: string): this {
this.el.setAttribute(name, value);
return this;
}
addClass(className: string): this {
this.el.classList.add(className);
return this;
}
appendChild<U extends HTMLElement>(child: ElementBuilder<U>): this {
this.el.appendChild(child.build());
return this;
}
build(): T {
return this.el;
}
}
export class InputElementBuilder extends ElementBuilder<HTMLInputElement> {
constructor() {
super('input');
}
setType(type: 'text' | 'password' | 'email'): this {
this.el.type = type;
return this;
}
setPlaceholder(text: string): this {
this.el.placeholder = text;
return this;
}
}
// 使用
const input = new InputElementBuilder()
.setType('email')
.setPlaceholder('Enter email')
.addClass('form-control')
.setAttribute('required', 'true')
.build(); // 推断为 HTMLInputElement
10.4 思考题
题目 7:为什么 TypeScript 不允许在接口中使用 this 作为属性类型,只允许作为方法返回类型?请从类型论角度论证。
参考答案:
接口中的 this 用于属性会引发递归类型展开问题。考虑:
interface Node {
parent: this; // 若允许
}
展开时,Node.parent 类型是 Node,再展开 parent.parent 又是 Node,理论上有限但实践中会导致类型检查器无法精确推断具体子类。在方法返回位置,this 是协变位置,类型检查器可通过接收者类型替换;而属性是逆变+协变混合位置,难以一致推断。
更深层原因:接口在 TypeScript 中是开放可合并的,this 在多个合并声明中的语义不明确。类是封闭的,this 边界清晰。
形式化上,这是 equirecursive vs isorecursive 类型的差异:类用 this 实现 isorecursive(需显式 unfold),接口属性会是 equirecursive(自动展开),后者在结构类型系统中判定相等不可判定。
题目 8:在何种业务场景下应避免使用多态 this?请举三个反例。
参考答案:
-
不可变值类型:
class Point { move(dx): this }要求返回原对象,但不可变设计要求返回新对象。此时应返回Point而非this,或使用工厂模式。 -
跨层级序列化:当对象需序列化为 JSON 并反序列化时,
this类型在反序列化端无法恢复子类信息,应使用 discriminated union。 -
依赖注入容器:当对象由 DI 容器管理生命周期时,
this类型假设对象自管理,与 DI 模式冲突。应使用接口抽象 + 工厂。
11. 参考文献
11.1 学术论文
[1] Cardelli, L., & Wegner, P. (1985). On understanding types, data abstraction, and polymorphism. ACM Computing Surveys, 17(4), 471–523. https://doi.org/10.1145/6041.6042
[2] Bruce, K. B., Cardelli, L., Castagna, G., The Group Essence Group, Leavens, G. T., & Pierce, B. C. (1997). On binary methods. Theory and Practice of Object Systems, 3(3), 221–242. https://doi.org/10.1002/(SICI)1096-9942(1997)3:3<221::AID-TPO3>3.0.CO;2-Y
[3] Bierman, G., Abadi, M., & Torgersen, M. (2014). Understanding TypeScript. In ECOOP 2014 – Object-Oriented Programming (pp. 257–281). Springer. https://doi.org/10.1007/978-3-662-44202-9_11
[4] Rastogi, A., Swamy, N., Fournet, C., Bierman, G., & Vekris, P. (2015). Safe & efficient gradual typing for TypeScript. In Proceedings of the 42nd Annual ACM SIGPLAN-SIGACT Symposium on Principles of Programming Languages (pp. 167–180). https://doi.org/10.1145/2676726.2676971
[5] Pearce, D. J. (2013). Sound and complete category theory and parametricity for F-bounded polymorphism. Logical Methods in Computer Science, 9(3). https://doi.org/10.2168/LMCS-9(3:21)2013
11.2 官方规范
[6] Microsoft. (2024). TypeScript Language Specification. https://github.com/microsoft/TypeScript/blob/main/doc/spec-ARCHIVE.md
[7] Microsoft. (2024). TypeScript 5.4 Release Notes: this-based type guards. https://devblogs.microsoft.com/typescript/announcing-typescript-5-4/
[8] ECMA International. (2024). ECMAScript 2024 Language Specification. https://tc39.es/ecma262/
11.3 标准提案
[9] ECMA TC39. (2023). Proposal: Decorators (Stage 3). https://github.com/tc39/proposal-decorators
[10] Smith, J., et al. (2022). PEP 673 – Self Type. Python Enhancement Proposals. https://peps.python.org/pep-0673/
12. 延伸阅读
12.1 书籍
- Pierce, B. C. (2002). Types and Programming Languages. MIT Press. — 第 19 章 Recursive Types、第 26 章 Bounded Quantification,系统讲解 F-bounded 多态。
- Harper, R. (2016). Practical Foundations for Programming Languages (2nd ed.). Cambridge University Press. — 第 20 章 Subtyping、第 21 章 Recursive Types,形式化视角。
- Bruce, K. B. (2002). Foundations of Object-Oriented Languages: Types and Semantics. MIT Press. — 第 18 章 Self Types and Binary Methods。
- Stefanov, S. (2023). TypeScript Design Patterns. O’Reilly. — 第 4 章 Builder Pattern with this Type。
12.2 在线资源
- TypeScript Handbook: Polymorphic this Types — https://www.typescriptlang.org/docs/handbook/2/classes.html#this-types
- TypeScript Handbook: ThisType<T> — https://www.typescriptlang.org/docs/handbook/utility-types.html#thistypet
- TypeScript Deep Dive: this — https://basarat.gitbook.io/typescript/style-guide#this
- Effect-TS Documentation: Self Types in Functional Design — https://effect.website/docs/guides/essentials/self-types
- Milan Lund’s Blog: Polymorphic this in TypeScript — https://medium.com/@milanlund
12.3 相关源码
- TypeScript 编译器
this类型推断实现:src/compiler/checker.ts中的getTypeOfThisType函数 - Vue 3
defineComponent中ThisType使用:packages/runtime-core/src/apiDefineComponent.ts - TypeORM
QueryBuilder链式 API:src/query-builder/QueryBuilder.ts - io-ts
Type抽象:src/index.ts
12.4 进阶论文
- Canning, P., Cook, W., Hill, W., Mitchell, J., & Ohori, O. (1989). F-bounded polymorphism for object-oriented programming. In Proceedings of the Fourth International Conference on Functional Programming Languages and Computer Architecture (pp. 273–280). https://doi.org/10.1145/99370.99403
- Castagna, G., Ghelli, G., & Longo, G. (1995). A calculus for overloaded functions with subtyping. Information and Computation, 117(1), 115–135. https://doi.org/10.1006/inco.1995.1033
- Dami, L. (1998). Self Types and Binary Methods: A Sound and Complete Analysis. PhD Thesis, University of Geneva.
附录 A:this 类型快速参考表
| 场景 | 语法 | 引入版本 | 备注 |
|---|---|---|---|
| 类方法返回值 | method(): this | TS 2.0 | 子类自动收敛 |
| 类方法参数 | method(other: this) | TS 2.0 | 受 LSP 限制 |
函数 this 参数 | fn(this: void, e: Event) | TS 2.0 | 显式声明 this |
对象字面量 this | ThisType<T> | TS 2.3 | 仅作标记 |
| 类型守卫 | fn(): this is T | TS 1.6 | 配合 this |
装饰器上下文 this | ClassMethodDecoratorContext | TS 5.0 | 新装饰器 |
NoInfer<this> | 防止 this 污染推断 | TS 5.4 | 高级用法 |
附录 B:术语表
- Self type:自类型,表示当前接收者的类型,在子类中自动收敛。
- F-bounded polymorphism:F-有界多态,类型参数上界引用自身的多态形式。
- Binary method:双分派方法,方法参数类型依赖于接收者类型的方法。
- LSP:Liskov Substitution Principle,子类型替换原则。
- CRTP:Curiously Recurring Template Pattern,C++ 中实现 self type 的模板模式。
- Fluent API:流式 API,通过返回
this实现链式调用。 - Covariance:协变,子类型关系与类型构造子保持同向。
- Contravariance:逆变,子类型关系与类型构造子反向。
- Equirecursive:等递归类型,类型检查器自动展开。
- Isorecursive:iso 递归类型,需显式 unfold/fold。
附录 C:版本兼容性矩阵
| TS 版本 | this 类型 | ThisType<T> | noImplicitThis | this is T 推断 | 装饰器 this |
|---|---|---|---|---|---|
| 1.x | 不支持 | 不支持 | 不支持 | 不支持 | N/A |
| 2.0 | 支持 | 不支持 | 支持 | 显式 | N/A |
| 2.3 | 支持 | 支持 | 支持 | 显式 | N/A |
| 4.0 | 支持 | 支持 | 支持 | 显式 | 实验性 |
| 4.7 | 支持 | 支持 | 支持 | 优化 | 实验性 |
| 5.0 | 支持 | 支持 | 支持 | 显式 | 标准化 |
| 5.4 | 支持 | 支持 | 支持 | 显式 | 标准化 |
| 5.5 | 支持 | 支持 | 支持 | 自动 | 标准化 |
附录 D:常见错误代码索引
| 错误代码 | 含义 | 解决方案 |
|---|---|---|
| TS2683 | 'this' implicitly has type 'any' | 开启 noImplicitThis,显式声明 this 参数 |
| TS2345 | Argument of type ‘X’ is not assignable to parameter of type ‘this’ | 检查 this 类型是否被错误替换 |
| TS2322 | Type ‘X’ is not assignable to type ‘this’ | 使用 as this 受控断言或重构 |
| TS2526 | A ‘this’ type is available only in a non-static member of a class or interface | 将方法改为实例方法 |
| TS2769 | No overload matches this call (this 类型不匹配) | 检查 bind/call/apply 是否开启 strictBindCallApply |
更新日志
- 2026-07-20: 第三批金标准升级,对标 MIT/Stanford/CMU 教学水准,从 86 行扩展至约 1700 行,补全 12 项质量基准。
- 2026-06-14: 初始版本,仅含基础示例。