Menu

instanceof w TypeScript: zawężanie klas i błędów

instanceof sprawdza w czasie działania, czy obiekt został utworzony przez daną klasę, a TypeScript zawęża zmienną do tej klasy. Jak działa z twoimi klasami i podklasami Error, dlaczego nie sprawdzi interfejsów ani aliasów typów i kiedy daje zaskakujące wyniki.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

value instanceof SomeClass to sprawdzenie JavaScriptu wykonywane w czasie działania: daje true, gdy SomeClass.prototype jest w łańcuchu prototypów obiektu, czyli gdy obiekt został utworzony przez new SomeClass (albo przez podklasę). Wewnątrz sprawdzenia TypeScript zawęża value do SomeClass.

Używaj instanceof dla obiektów utworzonych z klas, a typeof dla prymitywów. Tutaj potrzebne są oba: instanceof wydziela Date, a potem typeof dzieli to, co zostało.

Zawężanie własnych klas

instanceof zawęża do klasy w gałęzi true i usuwa ją w gałęzi false, więc unię klas można obsłużyć po jednym członie naraz.

Instancja podklasy przechodzi też sprawdzenie dla klasy rodzica: jeśli Square extends Rect, to new Square(2) instanceof Rect daje true. Gdy gałęzie się różnią, najpierw sprawdzaj najbardziej konkretną klasę.

Podklasy Error w catch

Najczęściej instanceof pojawia się w catch. W trybie strict złapana wartość ma typ unknown (rzucić można cokolwiek), a instanceof pozwala wrócić do otypowanego błędu.

Wynik:

404: /missing.txt
TypeError: path must be absolute

class X extends Error działa z instanceof na każdym targecie obsługiwanym przez TypeScript 7 (ES2015 i nowsze). Dawna rada, żeby w konstruktorze wywoływać Object.setPrototypeOf(this, X.prototype), dotyczyła kodu kompilowanego do ES5, a ten target TypeScript 7 usunął.

instanceof nie działa z interfejsami ani typami

Interfejsy i aliasy typów istnieją tylko dla kompilatora. Po kompilacji nie ma żadnej wartości User, z którą można porównać, więc TypeScript odrzuca takie sprawdzenie:

Kompilator zgłasza index.ts(8,24): error TS2693: 'User' only refers to a type, but is being used as a value here. Są dwa rozwiązania. Możesz sam sprawdzić kształt za pomocą type guard, czyli funkcji, która zwraca value is User:

Albo, jeśli to twój kod tworzy te obiekty, zrób z User klasę i twórz je przez new; wtedy instanceof działa. Predykaty i funkcje asercji szczegółowo opisuje strona o type guards.

Właściwy typ to nie właściwa instancja

TypeScript porównuje typy według struktury: literał obiektowy z tymi samymi składowymi co klasa da się przypisać do typu tej klasy. instanceof nie patrzy na strukturę. Przechodzi po łańcuchu prototypów, a obiekt, który nigdy nie powstał przez new, nie przechodzi sprawdzenia, nawet gdy kompilator akceptuje go jako ten typ.

Ostatnia linia jest ważna. structuredClone, JSON.parse(JSON.stringify(...)) i komunikaty między workerami zwracają zwykłe obiekty bez prototypu klasy, choć ich typ statyczny nadal może mówić Point. Gdy instancje klas przekraczają taką granicę, odtwórz je (new Point(copy.x, copy.y)), zanim zaczniesz polegać na instanceof albo na metodach.

instanceof i prymitywy

Prymitywy (string, number, boolean...) nie są obiektami i nie mają łańcucha prototypów, więc "hi" instanceof String daje false. TypeScript wychwytuje ten błąd, gdy tylko może: gdy po lewej stronie stoi wartość typu string, instanceof to błąd TS2358, The left-hand side of an 'instanceof' expression must be of type 'any', an object type or a type parameter. Dla prymitywów używaj typeof.

WartośćSprawdzenie instanceofWynik
new Date()instanceof Datetrue
[1, 2]instanceof Arraytrue (ale lepiej Array.isArray)
new TypeError("x")instanceof Errortrue (podklasa)
{ x: 1, y: 2 }instanceof Pointfalse (nigdy nie skonstruowany)
Object.create(null)instanceof Objectfalse (bez prototypu)
"hi"instanceof Stringfalse (prymityw)

Wartości z innego realmu

instanceof porównuje z jednym konkretnym obiektem konstruktora. Kod działający w innym realmie (iframe albo kontekst vm w Node) ma własne Array, Error i Date, więc tablica utworzona tam nie przechodzi tutaj instanceof Array. To samo dzieje się, gdy w node_modules trafią dwie kopie tego samego pakietu npm: każda kopia ma własną klasę, a instancja z jednej nie przechodzi instanceof względem drugiej. Dla tablic Array.isArray działa między realmami. Dla twoich własnych typów sprawdzanie właściwości (type guard albo pole kind) całkowicie omija ten problem.

Najczęściej zadawane pytania

Jak sprawdzić w TypeScript, czy obiekt jest instancją klasy?

Użyj value instanceof ClassName. To sprawdzenie w czasie działania (zwykły JavaScript), a TypeScript zawęża value do ClassName wewnątrz if. Działa z wbudowanymi klasami, takimi jak Date, Map i Error, a także z twoimi własnymi.

Czy w TypeScript można użyć instanceof z interfejsem?

Nie. Interfejsy i aliasy typów są usuwane, gdy TypeScript kompiluje kod do JavaScriptu, więc w czasie działania nie ma z czym porównywać. x instanceof User z interfejsem User to błąd TS2693, "'User' only refers to a type, but is being used as a value here." Zamiast tego sprawdź właściwości funkcją type guard albo zrób z User klasę, jeśli to ty tworzysz te obiekty.

Dlaczego instanceof zwraca false dla obiektu właściwego typu?

Typy w TypeScript są strukturalne: literał obiektowy { x: 1, y: 2 } da się przypisać do typu klasy Point, jeśli ma te same składowe. Ale instanceof sprawdza łańcuch prototypów, a literał nigdy nie powstał przez new Point, więc wynik to false. To samo dzieje się z instancjami klas, które przeszły przez JSON, structuredClone albo kanał komunikatów: wracają jako zwykłe obiekty.

Czy instanceof działa z własnymi klasami Error w TypeScript?

Tak, z każdym nowoczesnym targetem. class NotFound extends Error {}, a potem err instanceof NotFound daje true. Dawny problem, w którym wynik był false, dotyczył tylko kodu kompilowanego do ES5, a TypeScript 7 nie obsługuje już targetu ES5.

Dlaczego "hello" instanceof String daje false?

Literał stringa to prymityw, a nie obiekt, więc nie ma łańcucha prototypów do sprawdzenia. instanceof String daje true tylko dla obiektów opakowujących utworzonych przez new String(). TypeScript odrzuca instanceof na wartości typu string (TS2358); dla prymitywów używaj typeof value === "string".

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ