Перейти к содержимому
Теория · Классы · урок 7 из 12

get, set и static

На стене дома по нашему чертежу висит термостат. На табло видно число, а ручкой его меняют. Но за табло работает механизм: ручка не повернётся ниже допустимого, а табло может показать температуру и в других единицах. Методы get и set из JavaScript устроены так же: снаружи это обычное свойство, а при чтении и записи работает код.

TypeScript добавляет к ним типы:

TypeScript
class Temperature {
    #celsius = 0;

    get celsius(): number {
        return this.#celsius;
    }

    set celsius(value: number) {
        this.#celsius = Math.max(value, -273.15);
    }
}

const t = new Temperature();
t.celsius = -300;
console.log(t.celsius); // -273.15

Разберём пример по строкам:

  1. Поле #celsius хранит значение, и снаружи его не видно.
  2. get celsius(): number — геттер. Для кода снаружи t.celsius выглядит как поле типа number.
  3. set celsius(value: number) — сеттер. Запись t.celsius = -300 вызывает его, и он не пускает значение ниже абсолютного нуля.
  4. Программа печатает -273.15.

Типы у get и set

У геттера указывают тип результата, у сеттера — тип параметра. tsc проверяет по ним каждое чтение и каждую запись. Например, t.celsius = "20" не пройдёт: сеттер ждёт число.

TEXT
error TS2322: Type 'string' is not assignable to type 'number'.

Если у параметра сеттера не написать тип, TypeScript возьмёт его из результата геттера с тем же именем.

Здесь аналогия с термостатом перестаёт работать. Табло показывает число, которое где-то хранится, а геттер ничего не хранит: он заново выполняет свой код при каждом чтении.

Только get

Свойство с геттером, но без сеттера, для tsc — свойство только для чтения:

TypeScript
class Circle {
    radius = 1;

    get area(): number {
        return Math.PI * this.radius ** 2;
    }
}

const c = new Circle();
c.area = 10; // ✗
TEXT
error TS2540: Cannot assign to 'area' because it is a read-only property.

Ошибка та же, что при записи в поле readonly. А что будет при запуске, зависит от способа. В JavaScript, который пишет tsc, первая строка — "use strict";, и программа остановится на строке c.area = 10:

TEXT
TypeError: Cannot set property area of #<Circle> which has only a getter

node file.ts строку "use strict"; не добавляет, и там запись пропадёт молча: c.area по-прежнему вернёт площадь круга.

static-члены

Теперь второй, отдельный инструмент урока: с get и set он не связан. Вернёмся к чертежу. На нём самом, а не на каком-то доме, можно вести запись: «по этому чертежу построено 12 домов». Такая запись одна на весь чертёж. Статический член класса устроен так же: он принадлежит самому классу, и он один, сколько бы объектов ни создали.

TypeScript
class Ticket {
    static issued = 0;
    id: number;

    constructor() {
        Ticket.issued += 1;
        this.id = Ticket.issued;
    }
}

const a = new Ticket();
const b = new Ticket();
console.log(a.id, b.id, Ticket.issued); // 1 2 2
  1. static issued = 0 — одно поле на весь класс, его тип number выведен из значения.
  2. Конструктор увеличивает общий счётчик через имя класса: Ticket.issued += 1.
  3. Обычное поле id у каждого билета своё: оно получает текущее значение счётчика.
  4. Программа печатает 1 2 2: у билетов номера 1 и 2, а счётчик один, и в нём 2.

Методы тоже бывают статическими. Внутри static-метода this — это сам класс, поэтому static reset(): void { this.issued = 0; } обнулит общий счётчик.

На собеседовании часто спрашивают, чем статический член отличается от члена экземпляра. Ответ: статический член один на весь класс, и к нему обращаются через имя класса. Член экземпляра у каждого объекта свой, его создаёт new, а внутри методов к нему обращаются через this.

Частая ошибка

К статическому полю обратились через this, как к обычному:

TypeScript
class Ticket {
    static issued = 0;

    constructor() {
        this.issued += 1; // ✗
    }
}
TEXT
error TS2576: Property 'issued' does not exist on type 'Ticket'. Did you mean to access the static member 'Ticket.issued' instead?

В конструкторе this — новый билет, а счётчик висит на чертеже, не на доме. Сообщение само подсказывает исправление: пишите Ticket.issued.

Главное

  • У get указывают тип результата, у set — тип параметра, и tsc проверяет по ним каждое чтение и запись.
  • Свойство только с get — только для чтения: запись даёт ошибку TS2540.
  • static-член один на весь класс, и к нему обращаются через имя класса: Ticket.issued.
  • Через экземпляр static-член не виден: ошибка TS2576; а в static-методе this — сам класс.