Kodokon kodokon.com

重载、带类型的 this 与型变

理解重载解析、this 伪参数,以及支配函数可赋值性的型变规则。

10 分钟 · 3 题

在 Kodokon 中打开本课

重载函数暴露一组公开签名,后面跟着单个实现签名。一个关键的规范要点:后者从外部不可见;只有声明的重载才能被调用。解析会从上到下遍历签名,并保留第一个兼容的:始终把最具体的签名放在最前面,否则它永远不会被选中。

TYPESCRIPT
function parse(input: string): number;
function parse(input: string[]): number[];
function parse(input: string | string[]) {
  return Array.isArray(input)
    ? input.map((s) => s.length)
    : input.length;
}

const single = parse("abc");      // number
const many = parse(["a", "bb"]);  // number[]
每次调用都解析到一个精确的重载。

TypeScript 允许你在首位声明一个 this 伪参数。它在生成的 JavaScript 中被完全抹除:它是对调用上下文的一个纯静态约束,在启用 noImplicitThis 时进行检查。ThisParameterTypeOmitThisParameter 工具类型让你能提取或移除它,这对于为打算配合 callapplybind 使用的函数建立类型至关重要。

TYPESCRIPT
function greet(this: { name: string }, msg: string) {
  return `${msg}, ${this.name}`;
}

type Ctx = ThisParameterType<typeof greet>;
// { name: string }
type Fn = OmitThisParameter<typeof greet>;
// (msg: string) => string

const user = { name: "Ada", greet };
user.greet("Hello"); // "Hello, Ada"

// greet("Hi"); // error: missing this context
this 参数约束的是调用点,而非生成的 JS。

型变描述可赋值性如何通过一个类型构造器传播。对于函数,返回类型是协变的,而在启用 strictFunctionTypes 时,参数是逆变的:一个接受更宽类型的函数可以替换一个接受更窄类型的函数,反之绝不可以。一个刻意的例外:以简写语法声明的方法的参数仍然是双变的,这是一个历史妥协,没有它 Array<Dog> 就无法赋值给 Array<Animal>。自 4.7 起,你可以用 inout 来标注期望的型变,例如 interface Producer<out T>,编译器会对其进行验证。

TYPESCRIPT
interface Animal { name: string }
interface Dog extends Animal { bark(): void }

declare let f: (a: Dog) => void;
declare let g: (a: Animal) => void;

f = g; // OK: g accepts a wider type, that is safe
// g = f; // error with strictFunctionTypes

interface Sink { accept(a: Animal): void }
interface DogSink { accept(a: Dog): void }
declare let s: Sink;
declare let d: DogSink;

d = s; s = d; // both compile: bivariance!
严格的函数属性、双变的方法。

知识检测

确认你已牢记本课的重点内容。

  1. 调用一个重载函数时,编译器会挑选哪个签名?
    • 最通用的签名
    • 声明顺序中第一个兼容的签名
    • 实现签名
    • 所有签名的联合
  2. 函数的 this 参数在生成的 JavaScript 中会怎样?
    • 它变成一个真正的首个参数
    • 它消失了:它是一个纯静态的标注
    • 它被一个对 bind 的调用替换
    • 如果缺失,它会导致运行时错误
  3. 在 strictFunctionTypes 下,哪种语法能享有严格的逆变参数检查?
    • 简写语法的方法,例如 m(a: X): void
    • 函数类型的属性,例如 m: (a: X) => void
    • 两种语法都可以
    • 两者都不可以