Object.prototype.toString()
Two jobs in one method. Directly it produces the least helpful string in JavaScript; borrowed with .call it is the most reliable type check the language has ever had.
Demo
Each value reports its internal type, and the two marked cases are the ones typeof gets wrong. An array is '[object Array]' here while typeof says 'object'; null is '[object Null]' while typeof famously also says 'object'. That is why this borrowed call was the standard type check for two decades — it is the only mechanism that distinguishes arrays, dates, regexes and null from ordinary objects in a single expression. Note the demo has to use .call, because calling toString on the value directly would find that type own toString instead.
Common patterns
const typeOf = v => Object.prototype.toString.call(v).slice(8, -1);
Array.isArray(v); v instanceof Date; v === null;
class Box { get [Symbol.toStringTag]() { return "Box"; } }
Examples
Pitfalls
`Value: ${{a: 1}}`
`Value: ${JSON.stringify({a: 1})}`
Object.prototype.toString.call({[Symbol.toStringTag]: "Array"})
Array.isArray({[Symbol.toStringTag]: "Array"})
[1, 2].toString()
Object.prototype.toString.call([1, 2])
const o = {}; o.valueOf() === o
const money = {valueOf: () => 5}; money * 2
When to use
- A single expression that distinguishes every built-in type
- Writing a general-purpose type-tag helper
- Supporting very old runtimes where Array.isArray is absent
- Understanding where [object Object] came from
- Checking for an array → Array.isArray
- Checking for null → v === null
- Checking a class instance → instanceof
- Anything security-relevant → the tag can be forged
Notes
FAQ
Because string coercion calls toString, and the default implementation knows only the internal type — not your data. Use JSON.stringify for a readable form, or give the class a toString of its own.
JSON.stringify(obj); class P { toString() { return `P(${this.x})`; } }