Type-Checks in JavaScript wirken auf den ersten Blick wie ein gelöstes Problem — typeof x === 'string' und fertig. In Wahrheit sind die zwei Operatoren typeof und instanceof zwei sehr unterschiedliche Werkzeuge mit jeweils eigenen Stärken und überraschenden Lücken: typeof arbeitet auf der Ebene der Primitive-Typen und liefert einen kurzen String, instanceof läuft die Prototype-Chain hoch und sucht eine konkrete Konstruktor-Bindung. Beide haben Lücken: typeof null ist historisch 'object', instanceof Array scheitert über iframe-Grenzen, und für Custom-Klassen mischen sich beide Welten. Dieser Artikel zeigt jeden Operator im Detail, klassifiziert die Fallen und stellt die robusten Alternativen Array.isArray und Object.prototype.toString.call vor.
Zwei Operatoren, zwei Aufgaben
JavaScript bietet im Kern zwei Sprach-Operatoren zur Type-Prüfung. typeof ist ein unärer Operator, der einen String zurückgibt — gedacht für die Unterscheidung der sieben Primitive-Typen (plus function als Sonderfall). instanceof ist ein binärer Operator, der einen Boolean liefert — gedacht für die Frage, ob ein Wert irgendwo in seiner Prototype-Chain eine bestimmte Konstruktor-Funktion trägt. Die zwei Operatoren ergänzen sich:
typeofist günstig, sicher (wirft fast nie) und arbeitet auf jedem Wert — auch auf undefinierten Identifiern.instanceofist aussagekräftiger für eigene Klassen-Hierarchien und liefert auch bei Vererbung das richtige Ergebnis.
In der Praxis braucht ein robuster Type-Check meist beide Operatoren — und für einige Fälle (Arrays, Errors aus iframes, Built-in-Types wie Date oder RegExp) sogar zusätzlich Array.isArray oder Object.prototype.toString.call.
typeof: der String-basierte Primitive-Check
typeof gibt immer einen String zurück. Es gibt genau acht mögliche Rückgabewerte, festgelegt in der ECMAScript-Spezifikation. Sieben davon decken die Primitive-Typen ab; 'function' ist ein Sonderfall für callable Objects, und 'object' deckt alles andere ab — Arrays, Plain Objects, Date-Instanzen, null, RegExp, Map, Set und so weiter.
// 1. undefined
console.log(typeof undefined); // 'undefined'
// 2. boolean
console.log(typeof true); // 'boolean'
// 3. number
console.log(typeof 42); // 'number'
console.log(typeof NaN); // 'number' (NaN ist Number!)
console.log(typeof Infinity); // 'number'
// 4. bigint
console.log(typeof 42n); // 'bigint'
// 5. string
console.log(typeof 'abc'); // 'string'
// 6. symbol
console.log(typeof Symbol()); // 'symbol'
// 7. function (Sonderfall — eigentlich Object)
console.log(typeof function () {}); // 'function'
console.log(typeof class C {}); // 'function'
// 8. object (Sammelbecken — auch null, []!)
console.log(typeof {}); // 'object'
console.log(typeof []); // 'object'
console.log(typeof null); // 'object' (Bug)
console.log(typeof new Date()); // 'object'undefined
boolean
number
number
number
bigint
string
symbol
function
function
object
object
object
objectDer 'function'-Wert ist eine bewusste Bequemlichkeit: callable Werte sind technisch Objects, aber für die häufige Frage „ist das hier aufrufbar?" wäre eine Prüfung auf 'object' plus zusätzlicher Test umständlich. typeof fn === 'function' deckt das in einem Schritt ab.
Klassische typeof-Fallen
Drei Sonderfälle bei typeof sorgen seit Jahrzehnten für Bugs:
typeof null === 'object'— ein historischer Fehler aus der allerersten JavaScript-Version (1995):nullwurde mit demselben internen Type-Tag kodiert wie Objects. Die Spezifikation bezeichnet diesen Wert ausdrücklich als „willful violation" — er bleibt aus Web-Kompatibilität erhalten.typeof [] === 'object'— Arrays sind aus Sprachsicht ein Sub-Type von Object;typeofunterscheidet sie nicht. Für Array-Erkennung braucht esArray.isArray.typeof NaN === 'number'—NaNist semantisch „keine gültige Zahl", aber technisch ein IEEE-754-Float und damit Number. Wer auf „echte Zahl" prüft, muss zusätzlichNumber.isFinite(x)nutzen.
// Falle 1: null ist 'object'
function isObject(x) {
return typeof x === 'object'; // unzureichend!
}
console.log(isObject(null)); // true — falsch
// Korrekt:
function isPlainObject(x) {
return typeof x === 'object' && x !== null && !Array.isArray(x);
}
console.log(isPlainObject(null)); // false
console.log(isPlainObject([])); // false
console.log(isPlainObject({})); // true
// Falle 2: Array ist 'object'
console.log(typeof [1, 2, 3]); // 'object' — Array nicht erkennbar
console.log(Array.isArray([1, 2, 3])); // true — robust
// Falle 3: NaN ist 'number'
const result = Number('abc');
console.log(typeof result); // 'number'
console.log(Number.isNaN(result)); // true — NaN-Check getrennt nötigtrue
false
false
true
object
true
number
truetypeof ist Reference-sicher — fast immer
Eine einzigartige Eigenschaft von typeof: er ist der einzige Operator, der nicht wirft, wenn der Identifier komplett undeklariert ist. Das war historisch wichtig, um Feature-Detection im Browser zu ermöglichen — bevor man eine Variable wie globalThis benutzte, prüfte man typeof globalThis !== 'undefined'.
// undeklarierte Variable: KEIN ReferenceError
console.log(typeof nieDefinierteVariable); // 'undefined'
// direkter Zugriff: ReferenceError
try {
console.log(nieDefinierteVariable);
} catch (err) {
console.log(err.constructor.name); // 'ReferenceError'
}
// klassische Feature-Detection
if (typeof window !== 'undefined') {
// Browser-Code
}
if (typeof globalThis !== 'undefined') {
// alle modernen Engines
}undefined
ReferenceErrorAber Vorsicht — TDZ-Falle: für let- und const-Variablen ist typeof nicht sicher. Innerhalb der Temporal Dead Zone (zwischen Block-Eintritt und Initialisierung) wirft auch typeof einen ReferenceError. Der Reference-Schutz gilt nur für komplett undeklarierte Identifier.
// Fall 1: undeklariert — typeof ist sicher
console.log(typeof nichtVorhanden); // 'undefined'
// Fall 2: let/const VOR der Deklaration — typeof wirft!
try {
console.log(typeof xLet); // ReferenceError (TDZ)
} catch (err) {
console.log(err.message);
}
let xLet = 1;instanceof: der Prototype-Chain-Check
instanceof prüft, ob Constructor.prototype irgendwo in der Prototype-Chain des Objekts vorkommt. Das macht den Operator stark für eigene Klassen-Hierarchien: bei Vererbung gibt er für die Basis-Klasse und alle Sub-Klassen true zurück.
class Animal {
constructor(name) { this.name = name; }
}
class Dog extends Animal {
bark() { return 'wuff'; }
}
class Poodle extends Dog {}
const rex = new Poodle('Rex');
console.log(rex instanceof Poodle); // true
console.log(rex instanceof Dog); // true (geerbt)
console.log(rex instanceof Animal); // true (geerbt)
console.log(rex instanceof Object); // true (Wurzel der Chain)
// Negative Prüfung
class Cat {}
console.log(rex instanceof Cat); // falsetrue
true
true
true
falseDer Operator ist mächtig für Polymorphie: eine Funktion kann sich auf den gemeinsamen Basis-Typ verlassen, ohne den konkreten Sub-Typ zu kennen.
instanceof-Fallen: Primitives und Realms
instanceof hat zwei systematische Schwachpunkte. Erstens: für Primitive-Werte liefert er immer false, auch wenn ein passender Wrapper existiert. Ein 'abc' instanceof String ist false, weil der Primitive-String kein Object ist und keine Prototype-Chain hat.
console.log('abc' instanceof String); // false (Primitive)
console.log(42 instanceof Number); // false
console.log(true instanceof Boolean); // false
// nur explizit erzeugte Wrapper-Objects:
console.log(new String('abc') instanceof String); // true
console.log(new Number(42) instanceof Number); // true
// Praxis: NIE mit instanceof prüfen, ob ein Wert ein String ist
// typeof ist hier korrekt:
console.log(typeof 'abc' === 'string'); // truefalse
false
false
true
true
trueZweitens: Realms. Jeder iframe, jeder Worker, jeder VM-Context in Node hat eine eigene Kopie aller globalen Konstruktoren — eigenes Array, eigenes Date, eigenes Error. Ein Array, das aus einem iframe stammt, hat als Prototype das Array.prototype dieses iframes, nicht das des Haupt-Frames. arr instanceof Array aus Sicht des Hauptframes ist dann false.
// Skizze: iframe-Szenario im Browser
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
const iframeArray = iframe.contentWindow.Array;
const fremdesArray = new iframeArray(1, 2, 3);
// Hauptframe sieht das Array als Object aus fremdem Realm:
console.log(fremdesArray instanceof Array); // false (!)
console.log(Array.isArray(fremdesArray)); // true — Realm-sicher
// Same problem: Errors, Dates, RegExps aus iframes
// Lösung: Object.prototype.toString.call(...) oder Array.isArraySymbol.hasInstance ist die dritte Falle bzw. das mächtige Feature: eine Klasse kann den instanceof-Algorithmus überschreiben, indem sie eine static [Symbol.hasInstance]-Methode definiert. Das ist nützlich für „Brand-Checks" mit privaten Feldern.
class GeradeZahl {
static [Symbol.hasInstance](x) {
return typeof x === 'number' && x % 2 === 0;
}
}
console.log(2 instanceof GeradeZahl); // true
console.log(3 instanceof GeradeZahl); // false
console.log(4 instanceof GeradeZahl); // true
// Brand-Check über privates Feld:
class Marke {
#brand = true;
static [Symbol.hasInstance](x) {
try { return #brand in x; } catch { return false; }
}
}true
false
trueObject.prototype.toString.call(x) — die Allzweckwaffe
Vor Array.isArray und vor Symbol.toStringTag war Object.prototype.toString.call(x) das Standard-Pattern für robuste Type-Identifikation. Es funktioniert auf jedem Wert (auch null und undefined), liefert einen exakt definierten String der Form '[object Type]' und ist realm-sicher, solange das Ziel-Object kein eigenes Symbol.toStringTag setzt.
const toStr = Object.prototype.toString;
console.log(toStr.call(undefined)); // '[object Undefined]'
console.log(toStr.call(null)); // '[object Null]'
console.log(toStr.call(true)); // '[object Boolean]'
console.log(toStr.call(42)); // '[object Number]'
console.log(toStr.call('abc')); // '[object String]'
console.log(toStr.call([])); // '[object Array]'
console.log(toStr.call({})); // '[object Object]'
console.log(toStr.call(new Date())); // '[object Date]'
console.log(toStr.call(/x/)); // '[object RegExp]'
console.log(toStr.call(new Map())); // '[object Map]'
console.log(toStr.call(new Error())); // '[object Error]'
console.log(toStr.call(function () {})); // '[object Function]'[object Undefined]
[object Null]
[object Boolean]
[object Number]
[object String]
[object Array]
[object Object]
[object Date]
[object RegExp]
[object Map]
[object Error]
[object Function]Eine kleine Helper-Funktion macht daraus einen einzigen, präzisen Type-Check:
function getType(value) {
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}
console.log(getType(null)); // 'null'
console.log(getType([])); // 'array'
console.log(getType(new Date())); // 'date'
console.log(getType(new Map())); // 'map'
console.log(getType(/x/)); // 'regexp'null
array
date
map
regexpArray.isArray — der einzige zuverlässige Array-Check
Mit ES5 (2009) bekam JavaScript einen dedizierten Array-Check, der die Realm-Schwäche von instanceof und die Unspezifität von typeof vermeidet. Array.isArray nutzt das interne [[Class]]-Tag und funktioniert über Realms hinweg — er ist heute die Standard-Antwort auf „ist das ein Array?".
console.log(Array.isArray([])); // true
console.log(Array.isArray([1, 2, 3])); // true
console.log(Array.isArray(new Array(3))); // true
// negative Fälle — nichts davon ist ein Array:
console.log(Array.isArray('abc')); // false (String ist iterable, kein Array)
console.log(Array.isArray({ length: 3 })); // false (Array-like, kein Array)
console.log(Array.isArray(arguments)); // false in normalen Functions
console.log(Array.isArray(new Set([1]))); // false (Set, kein Array)
// Realm-sicher: anders als instanceof
// const arrFromIframe = iframe.contentWindow.Array.of(1, 2);
// arrFromIframe instanceof Array; // false
// Array.isArray(arrFromIframe); // true ✓true
true
true
false
false
false
falseWann was nutzen: die Entscheidungs-Tabelle
| Frage / Use-Case | Tool der Wahl | Beispiel |
|---|---|---|
| „Ist das ein String / Number / …?" | typeof | typeof x === 'string' |
| „Ist das aufrufbar?" | typeof | typeof x === 'function' |
| „Ist das ein Array?" | Array.isArray | Array.isArray(x) |
| „Ist das eine Instanz meiner Klasse?" | instanceof | x instanceof MeineKlasse |
| „Ist das ein Built-in (Date, RegExp)?" | Object.prototype.toString.call | toStr.call(x) === '[object Date]' |
„Ist das null oder undefined?" | == mit null | x == null |
| „Ist das ein Plain Object?" | Kombination | typeof x === 'object' && x !== null && !Array.isArray(x) |
| „Ist das eine echte Zahl?" | Number.isFinite | Number.isFinite(x) |
| „Ist das NaN?" | Number.isNaN | Number.isNaN(x) |
| „Ist das ein Element aus einem iframe?" | Object.prototype.toString.call | toStr.call(x) === '[object HTMLElement]' |
Type-Guards für TypeScript-ähnliche Sicherheit
Auch ohne TypeScript lassen sich präzise Type-Guards bauen, die einen Wert verlässlich auf einen konkreten Typ einschränken. Mit JSDoc-Type-Predicates verstehen IDEs und der TypeScript-Compiler die Verfeinerung sogar in reinem JavaScript.
/** @returns {value is string} */
function isString(value) {
return typeof value === 'string';
}
/** @returns {value is number} */
function isFiniteNumber(value) {
return typeof value === 'number' && Number.isFinite(value);
}
/** @returns {value is unknown[]} */
function isArray(value) {
return Array.isArray(value);
}
/** @returns {value is Record<string, unknown>} */
function isPlainObject(value) {
if (typeof value !== 'object' || value === null) return false;
if (Array.isArray(value)) return false;
const proto = Object.getPrototypeOf(value);
return proto === Object.prototype || proto === null;
}
// Anwendung in Pipelines:
function summe(werte) {
if (!isArray(werte)) throw new TypeError('werte muss ein Array sein');
return werte
.filter(isFiniteNumber)
.reduce((acc, n) => acc + n, 0);
}
console.log(summe([1, 2, 'x', NaN, 4])); // 7
console.log(isPlainObject({ a: 1 })); // true
console.log(isPlainObject(new Date())); // false (Date.prototype-Chain)7
true
falseDer wichtigste Punkt: ein guter Type-Guard ist single-purpose und gibt ein klares Boolean zurück. Komplexe Mehrfach-Checks gehören in benannte Helper, damit Aufruf-Stellen lesbar bleiben.
Type-Check-Fallen
typeof null === 'object' ist ein historischer Bug
Aus der allerersten JavaScript-Version (1995) erhalten geblieben aus Web-Kompatibilität. Wer auf „echtes Object" prüft, muss x !== null ergänzen — sonst rutscht null versehentlich durch.
typeof function === 'function' — der einzige Sonderfall
Functions sind technisch Objects, aber typeof liefert für callable Werte den eigenen String 'function'. Bequem, aber Vorsicht: auch Class-Konstruktoren und gebundene Functions sind 'function'.
typeof bei undeklarierten Variablen wirft NICHT — bei TDZ schon
typeof nieDefiniert gibt 'undefined', ohne zu werfen. ABER: let x oder const x vor der Deklaration ist in der Temporal Dead Zone, und auch typeof wirft dort einen ReferenceError.
instanceof scheitert über Realm-Grenzen
Arrays, Errors, Dates aus iframes oder Workers haben einen anderen Prototype-Chain-Root als der Hauptframe. arr instanceof Array ist dann false — selbst wenn arr ein vollwertiges Array ist. Lösung: Array.isArray oder Object.prototype.toString.call.
Array.isArray ist realm-sicher — instanceof Array nicht
Array.isArray nutzt das interne [[Class]]-Tag und funktioniert auch für Arrays aus fremden Realms. Es ist heute der einzige korrekte Array-Check; instanceof Array sollte vermieden werden.
Object.prototype.toString.call(null) gibt '[object Null]'
Die einzige Methode, die für ALLE Werte (auch null und undefined) einen sinnvollen String liefert. War vor Array.isArray der Standard und bleibt für Built-ins wie Date, RegExp, Map die robusteste Lösung.
Symbol.hasInstance kann instanceof komplett überschreiben
Eine Klasse mit static Symbol.hasInstance { ... } definiert die instanceof-Logik selbst. Mächtig für Brand-Checks mit privaten Feldern, aber gefährlich: ein Aufrufer kann x instanceof Klasse nicht mehr trauen, ohne die Klasse zu kennen.
typeof BigInt-Wert ist 'bigint', NICHT 'number'
Seit ES2020 ist BigInt ein eigener Primitive-Typ. typeof 42n ist 'bigint'. Code, der nur auf typeof === 'number' prüft, übersieht BigInts — was bei numerischen Pipelines zu Bugs führt.
Symbol-Werte sind Primitives mit Object-ähnlicher API
typeof Symbol() === 'symbol', aber Symbols haben Methoden wie .description und .toString(). Sie verhalten sich wie eine Mischung — Type-Checks gehören weiterhin zu typeof, nicht zu instanceof.
Weiterführende Ressourcen
Externe Quellen
- typeof – MDN
- instanceof – MDN
- Array.isArray – MDN
- Symbol.hasInstance – MDN
- The typeof Operator – ECMAScript Spec