Проверки in и instanceof
На сортировочную ленту приехали коробки. Снаружи они одинаковые, картонные, и арка typeof про любую из них скажет одно: «коробка». Чтобы разобрать коробки дальше, нужны другие приметы. Например, есть ли у коробки ручка для переноски или стоит ли на ней клеймо завода, который её сделал.
С объектами в JavaScript так же: для обычного объекта, массива или даты typeof возвращает "object". Служба доставки хранит заказы двух видов: с курьером — у такого заказа есть телефон, и самовывоз — у него есть адрес пункта выдачи. Проверка typeof их не различает:
type Courier = { phone: string };
type Pickup = { address: string };
function contact(d: Courier | Pickup) {
if (typeof d === "object") {
return "call " + d.phone; // ✗
}
}error TS2339: Property 'phone' does not exist on type 'Courier | Pickup'.
Property 'phone' does not exist on type 'Pickup'.Условие typeof d === "object" истинно для обоих видов заказа, поэтому tsc ничего не сужает: в ветке по-прежнему Courier | Pickup. Нужна проверка, которая смотрит на сам объект.
Оператор in
Оператор in из JavaScript проверяет при запуске, есть ли у объекта свойство с таким именем. tsc читает ту же проверку и оставляет в ветке только те варианты объединения, у которых это свойство объявлено:
type Courier = { phone: string };
type Pickup = { address: string };
function contact(d: Courier | Pickup) {
if ("phone" in d) {
return "call " + d.phone;
}
return "pick up at " + d.address;
}
console.log(contact({ phone: "555-01" })); // call 555-01
console.log(contact({ address: "Main St 5" })); // pick up at Main St 5Давайте разберём пример по строкам:
"phone" in dпри запуске спрашивает у объекта, есть ли в нём ключphone.- Внутри
ifуdтипCourier: свойствоphoneобъявлено только у него. - После
returnосталсяPickup, иd.addressкомпилируется: сужение работает в обе стороны. - Программа печатает
call 555-01иpick up at Main St 5.
Это та самая ручка на коробке: проверка смотрит, есть ли у объекта нужная деталь. Классы для in не нужны: он работает с обычными объектными типами, описанными через type.
instanceof и Array.isArray
Другая примета — клеймо завода. Объекты, созданные через new, проверяют оператором instanceof. Он узнаёт, каким конструктором сделан объект: Date, Error или классом JavaScript, который вы написали сами. Массив проверяют функцией Array.isArray:
function year(d: Date | string) {
if (d instanceof Date) {
return d.getFullYear();
}
return Number(d.slice(0, 4));
}
function count(tags: string | string[]) {
if (Array.isArray(tags)) {
return tags.length;
}
return 1;
}
console.log(year(new Date(2024, 4, 1)), count(["a", "b"])); // 2024 2Программа печатает 2024 2. Разберём каждую проверку.
instanceof
Дата приходит объектом Date или строкой вроде "2025-01-31". Внутри if (d instanceof Date) у d тип Date, и метод getFullYear доступен. После return осталась строка, и d.slice компилируется. Вызов year(new Date(2024, 4, 1)) возвращает 2024.
Так же проверяют ошибку e, пойманную в catch: после e instanceof Error у e внутри ветки тип Error, и можно читать e.message.
Array.isArray
Теги приходят одной строкой или массивом строк. Array.isArray(tags) возвращает true только для массива, и tsc оставляет в ветке тип string[]. Вызов count(["a", "b"]) возвращает 2, а одна строка считается одним тегом.
Для массива typeof тоже даёт "object", поэтому здесь нужна именно Array.isArray.
in или instanceof
Этот вопрос часто задают на собеседовании. Коротко разница такая:
inпроверяет, есть ли у объекта свойство с таким именем, и подходит для любых объектов, в том числе описанных черезtype;instanceofпроверяет, каким конструктором создан объект, и работает только с классами.
Здесь аналогия с клеймом перестаёт работать. Клеймо ставят на коробку, а объектный тип ничего на объекте не оставляет. Объект, собранный литералом { phone: "555-01" }, при запуске не помнит, что в коде его называли Courier.
Частая ошибка
Объектный тип проверили через instanceof:
type Courier = { phone: string };
type Pickup = { address: string };
function contact(d: Courier | Pickup) {
if (d instanceof Courier) { // ✗
return "call " + d.phone;
}
return "pick up at " + d.address;
}error TS2693: 'Courier' only refers to a type, but is being used as a value here.Courier — тип, а типы при компиляции стираются. В JavaScript-файле не осталось ничего, с чем instanceof мог бы сравнить объект при запуске, поэтому tsc не пропускает проверку. Тип не сужается, и ошибки появятся ещё на d.phone и d.address. Для объектных типов из type пишите "phone" in d.
Главное
- Для обычного объекта и массива
typeofвозвращает"object", поэтому объекты различают другими проверками. "phone" in dоставляет в ветке варианты, у которых объявлено свойствоphone, а после неё — остальные.x instanceof Dateсужает тип доDate, аArray.isArray(x)— до массива.instanceofработает только с классами: тип изtypetsc в нём не пропустит.