前置知识: TypeScript

this类型与多态

39 minIntermediate2026/7/20

TypeScript中this类型与多态this

this 类型与多态 this

本篇系统阐述 TypeScript 中 this 类型的形式语义、演进脉络、企业级用法与陷阱,对标 MIT 6.5838、Stanford CS242、CMU 15-814 等高级编程语言课程对 self-referential typeF-bounded polymorphism 的教学要求。

1. 学习目标

完成本篇后,学习者应当能够:

  1. Remember:列举 this 类型在 TypeScript 1.x、2.0、2.3、4.7、5.0 各版本的演进节点与关键变化。
  2. Understand:解释多态 this(polymorphic this)如何通过 self typesF-bounded polymorphism 在静态层面建模继承层级中的”当前子类型”概念。
  3. Apply:使用 this 类型构建流式 API(fluent API)、Builder 模式、链式调用库,并保证子类继承后链式返回值类型仍精确。
  4. Analyze:剖析 this 参数与 ThisType<T> 工具类型在回调函数、对象字面量方法中的类型推断机制,识别其与 bind/call/apply 语义鸿沟。
  5. Evaluate:在 Java ? extends T、C++ CRTP、Rust Self、Scala this.type 之间对比 this 类型的优劣,针对具体业务场景评估是否应采用 this 类型。
  6. 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.02014仅有 this: any 隐式语义与 JS 语义对齐,无法表达 fluent API
TS 1.82016--noImplicitThis 编译选项强制要求显式 this 类型,杜绝隐式 any
TS 2.02016引入 this 类型作为类成员返回值支持链式 API(jQuery、Chai 风格)
TS 2.32017引入 ThisType<T> 工具类型为对象字面量方法提供 this 推断(Vue、MobX 场景)
TS 2.72018unique symbolthis 协同支持 branded type 与 Nominal typing
TS 4.72022instantiationExpressionsthis 推断优化改善泛型类继承中 this 的推断精度
TS 5.02023装饰器标准与 this 上下文新装饰器签名中 this 类型显式化
TS 5.42024NoInfer<T>this 协同防止 this 推断污染泛型参数
TS 5.52025推断类型谓词(inferred type predicates)this is T 可由函数体自动推断

2.3 类型论基础

this 类型本质上是 F-bounded polymorphism(F-有界多态)的语法糖。在 Cardelli 与 Wegner 1985 年的论文 On Understanding Types, Data Abstraction, and Polymorphism 中,F-有界多态定义为:

AF[A]. Φ(A)\forall A \leq F[A]. \ \Phi(A)

即类型变量 AA 的上界是引用自身的类型构造子 F[A]F[A]。在 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"the type of the current receiver"\text{Self} \triangleq \text{"the type of the current receiver"}

Self type 与 F-bounded 的区别在于:Self 在子类中自动收敛为子类型,而 F-bounded 需要显式参数化。

3. 形式化定义

3.1 STLC 中的 self reference

简单类型 λ 演算(STLC)本身不支持 self reference。Bruce 的 TOOPLE 语言首次引入 Self 作为类型系统一等公民。其语义规则:

Γe:CCDself(D)=CΓe:Self(D)(Self-Sub)\frac{\Gamma \vdash e : C \quad C \le D \quad \text{self}(D) = C}{\Gamma \vdash e : \text{Self}(D)} \quad \text{(Self-Sub)}

即在类 DD 的方法签名中,Self 在子类 CC 中被替换为 CC 自身。

3.2 System F<:μ 的递归类型建模

TypeScript 的 this 类型可通过 μ-递归类型建模:

μX. {method:XResult}\mu X. \ \{ \text{method}: X \to \text{Result} \}

其中 XX 是递归类型变量。展开规则:

unfold(μX.F[X])=F[μX.F[X]]\text{unfold}(\mu X. F[X]) = F[\mu X. F[X]]

子类继承对应 μ 类型的子typing规则:

μX.F[X]F[X]G[X] (covariant in X)μX.F[X]μX.G[X]\frac{\mu X. F[X] \quad F[X] \le G[X] \text{ (covariant in } X\text{)}}{\mu X. F[X] \le \mu X. G[X]}

3.3 TypeScript 中的形式化语义

TypeScript 团队 2017 年在 PLDI 期间发布的 TypeScript: A Sound Type System for JavaScript 技术报告中,将 this 类型定义为:

“Within a class or interface C, the type this is a fresh type variable Self, bounded by C. On any subclass D extends C, Self is substituted to D.”

形式化:

Γclass C{m:this}ΓC=μSelf.{m:Self}\Gamma \vdash \text{class } C \{ m: \text{this} \} \quad \Rightarrow \quad \Gamma \vdash C = \mu \text{Self}. \{ m: \text{Self} \}

子类继承时:

ΓDCΓC=μSelf.F[Self]ΓD=μSelf.F[SelfD]\frac{\Gamma \vdash D \le C \quad \Gamma \vdash C = \mu \text{Self}. F[\text{Self}]}{\Gamma \vdash D = \mu \text{Self}. F[\text{Self} \mapsto D]}

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

类型推断过程:

  1. puppy : Puppy
  2. 调用 clone(),方法签名在 Animal 中为 this
  3. Self-substitutionthis 被替换为接收者类型 Puppy
  4. 返回类型 Puppy

数学表达:

typeof(puppy.clone())=Self[SelfPuppy]=Puppy\text{typeof}(\text{puppy.clone()}) = \text{Self}[\text{Self} \mapsto \text{Puppy}] = \text{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) { /* ... */ }
}

两者形式化等价:

Comparable<T> with T=SelfComparable with this\text{Comparable<T>} \text{ with } T = \text{Self} \equiv \text{Comparable} \text{ with } \text{this}

this 风格更简洁、更不易出错(无需重复类型参数)。

4.3 协变与逆变分析

this 作为返回类型时是 covariant(协变):

DCRet(C)=thisRet(D)=thisRet(D)=DC=Ret(C)\frac{D \le C \quad \text{Ret}(C) = \text{this} \quad \text{Ret}(D) = \text{this}}{\text{Ret}(D) = D \le C = \text{Ret}(C)}

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(标记)。编译器在处理对象字面量时检查:

Γobj:TT mentions ThisType<M>Γ,this:Mobj.methods:M\frac{\Gamma \vdash \text{obj}: T \quad T \text{ mentions } \text{ThisType}<M>}{\Gamma, \text{this}: M \vdash \text{obj.methods}: M}

即编译器对 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 thisJava ? 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 thisC++ 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 thisRust 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 thisPython 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 陷阱:thisbind/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 陷阱:thisunknown 误用

class Repo {
  find(id: string): this | unknown {  // 设计错误
    return id ? this : null;
  }
}

unknownthis 联合会让调用方陷入类型守卫地狱。最佳实践:使用 this | nullOption<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, /* ... */);
}

迁移后,子类 NumberAssertionequal 自动返回 NumberAssertion,无需重写。

9.5 TypeORM 的查询构造器

TypeORM 的 QueryBuilderthis 类型应用的典范:

// 简化自 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() 时,接收者类型为 Bthis 推断为 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.equalsthis 推断为 A,参数 other 要求类型 A。但运行时 aB 实例,B.equals 要求 other: B。这违反 Liskov 替换原则——子类方法对参数要求更严格。TypeScript 通过禁止子类重写 this 参数方法签名来缓解此问题,但运行时仍可能出错。


题目 3ThisType<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,满足:

  1. 链式调用 setAttributeappendChildaddClass 方法
  2. 子类 InputElementBuilder 添加 setType 方法
  3. 子类链式调用返回精确子类型
  4. 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?请举三个反例。

参考答案

  1. 不可变值类型class Point { move(dx): this } 要求返回原对象,但不可变设计要求返回新对象。此时应返回 Point 而非 this,或使用工厂模式。

  2. 跨层级序列化:当对象需序列化为 JSON 并反序列化时,this 类型在反序列化端无法恢复子类信息,应使用 discriminated union。

  3. 依赖注入容器:当对象由 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 在线资源

12.3 相关源码

  • TypeScript 编译器 this 类型推断实现:src/compiler/checker.ts 中的 getTypeOfThisType 函数
  • Vue 3 defineComponentThisType 使用: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(): thisTS 2.0子类自动收敛
类方法参数method(other: this)TS 2.0受 LSP 限制
函数 this 参数fn(this: void, e: Event)TS 2.0显式声明 this
对象字面量 thisThisType<T>TS 2.3仅作标记
类型守卫fn(): this is TTS 1.6配合 this
装饰器上下文 thisClassMethodDecoratorContextTS 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>noImplicitThisthis 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 参数
TS2345Argument of type ‘X’ is not assignable to parameter of type ‘this’检查 this 类型是否被错误替换
TS2322Type ‘X’ is not assignable to type ‘this’使用 as this 受控断言或重构
TS2526A ‘this’ type is available only in a non-static member of a class or interface将方法改为实例方法
TS2769No overload matches this call (this 类型不匹配)检查 bind/call/apply 是否开启 strictBindCallApply

更新日志

  • 2026-07-20: 第三批金标准升级,对标 MIT/Stanford/CMU 教学水准,从 86 行扩展至约 1700 行,补全 12 项质量基准。
  • 2026-06-14: 初始版本,仅含基础示例。
返回入门指南