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. Утилиты ThisParameterType и OmitThisParameter позволяют извлечь его или убрать - это необходимо, чтобы типизировать функции, предназначенные для использования с call, apply или bind.

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 ожидаемую вариантность можно аннотировать через in и out, например 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
    • Оба синтаксиса
    • Ни один из них