typeof vs instanceof - dwa sposoby sprawdzenia typu w JS
JavaScript ma dwa podstawowe operatory do sprawdzania typu i każdy odpowiada na inne pytanie. typeof zwraca jeden z 8 stringów i ma znane pułapki (null, tablice). instanceof chodzi po łańcuchu prototypów i zawodzi dla obiektów z osadzonego <iframe>. Notatka o tym, kiedy którego użyć i dlaczego do tablic istnieje osobne Array.isArray.
Klasyczne pytanie: jak sprawdzić, czy zmienna to tablica?
const arr = [1, 2, 3];
typeof arr; // "object" - nie "array"
arr instanceof Array; // true - ale nie zawsze
Array.isArray(arr); // true - dedykowana metoda dla tablicSkąd ta rozbieżność? Bo typeof i instanceof odpowiadają na inne pytania. typeof mówi o szerokiej kategorii (prymityw vs obiekt vs funkcja). instanceof chodzi po łańcuchu prototypów. Jeśli wiesz, którego używasz i dlaczego, większość tzw. „pułapek typowania” przestaje istnieć.
#typeof - osiem możliwych wyników
typeof x to operator zwracający string opisujący typ wartości. Specyfikacja definiuje dokładnie 8 możliwych zwrotów:
| Wartość | Wynik |
|---|---|
undefined | "undefined" |
true, false | "boolean" |
42, NaN, Infinity | "number" |
42n | "bigint" |
"hello" | "string" |
Symbol() | "symbol" |
funkcja (() => {}) | "function" |
| wszystko inne (obiekty) | "object" |
Trzy pułapki, o których warto pamiętać:
typeof null; // "object" (!) - historyczny bug, nigdy nie naprawiony
typeof []; // "object" - tablica to specjalny rodzaj obiektu
typeof NaN; // "number" - NaN formalnie należy do typu Numbertypeof null === "object" zostało takie od pierwszej wersji JavaScriptu i nie zostanie naprawione ze względu na wsteczną kompatybilność. Jeśli chcesz odróżnić null od innego obiektu, sprawdzaj x === null osobno. Do tablic używaj Array.isArray(x), do NaN - Number.isNaN(x) (zob. notatkę o number quirks).
typeof nie rzuca błędu nawet dla niezdefiniowanych zmiennych - zwraca "undefined". To jedyny operator w JS przyjmujący argument, który nie istnieje. Wyjątek: dla let/const użytych przed deklaracją (tzw. temporal dead zone) typeof rzuca ReferenceError.
#instanceof - sprawdza łańcuch prototypów
x instanceof Konstruktor zwraca true, jeśli Konstruktor.prototype znajduje się gdziekolwiek w łańcuchu prototypów x. Silnik chodzi po łańcuchu w górę i sprawdza każdy poziom (zob. notatkę o prototype chain):
class Animal {}
class Dog extends Animal {}
const rex = new Dog();
rex instanceof Dog; // true (Dog.prototype jest bezpośrednio)
rex instanceof Animal; // true (Animal.prototype jest wyżej w łańcuchu)
rex instanceof Object; // true (Object.prototype na końcu łańcucha)W praktyce po instanceof sięgasz głównie do instancji własnych klas - dziedziczenie sprawdza się czysto.
Pod spodem składnia korzysta z Symbol.hasInstance - klasa może przeciążyć ten mechanizm i zwrócić co chce. W codziennym kodzie raczej go nie ruszasz, ale dobrze wiedzieć, że domyślny algorytm chodzenia po prototypach to tylko zachowanie domyślne, nie twardy wymóg języka.
Najważniejsza pułapka: instanceof nie działa dobrze, gdy obiekt pochodzi z innej strony niż ta, na której go sprawdzasz - np. ze strony osadzonej przez tag <iframe>. Każdy <iframe> ma własną, niezależną kopię Array, Date itd., więc tablica z iframe’a ma inny Array.prototype niż tablica z głównej strony - i instanceof Array zwraca false:
const arrFromFrame = window.frames[0].Array.from([1, 2, 3]);
arrFromFrame instanceof Array; // false (!) - tablica z iframe ma inny Array
Array.isArray(arrFromFrame); // true - działa zawszeDlatego do sprawdzania tablic używaj Array.isArray, nie instanceof Array. Array.isArray jest zdefiniowane tak, że działa niezależnie od tego, skąd pochodzi tablica.
Druga rzecz: instanceof zwraca false dla prymitywów ("abc" instanceof String to false), bo prymitywy nie mają łańcucha prototypów. Do prymitywów jest typeof.
#Kiedy którego użyć
W praktyce wszystko sprowadza się do dwóch reguł:
- Sprawdzasz prymityw (string, number, boolean, undefined, symbol, bigint) →
typeof. - Sprawdzasz konkretny rodzaj obiektu → dedykowana metoda jeśli istnieje (
Array.isArray,Number.isNaN,Number.isInteger), inaczejinstanceof.
Plus dwa wyjątki:
nullsprawdzaj osobno przezx === null(botypeof nullto"object").- Gdy obiekt może pochodzić z osadzonego
<iframe>, użyj dedykowanej metody alboObject.prototype.toString.call(x)- zwraca string typu"[object Array]"i działa wtedy, gdyinstanceofzawodzi.
#Co z tego zapamiętać
typeofzwraca jeden z 8 stringów - 6 prymitywów ("undefined","boolean","number","bigint","string","symbol"),"function"dla funkcji, plus"object"dla wszystkiego innego. Pamiętaj otypeof null === "object"itypeof [] === "object".instanceofchodzi po łańcuchu prototypów. Nie działa dla prymitywów ani dla obiektów pochodzących z osadzonego<iframe>. Dla tablic używajArray.isArray, nieinstanceof Array.
W codziennym kodzie rzadko będziesz potrzebować innego niż typeof i dedykowanych metod (Array.isArray, Number.isNaN). Ale gdy sprawdzenie typu da nieoczekiwany wynik, masz teraz mapę: każdy z dwóch operatorów odpowiada na inne pytanie.