Fixing the JavaScript typeof operator

Working with JavaScript’s typeof operator is a bit like operating a clapped-out old car (or an early model Dell Inspiron). It gets the job done (mostly) and you learn to work around the quirks – but you probably aspire to something better.

In this article I’ll give a brief overview of typeof before introducing a tiny new function which is a fully-loaded, more reliable alternative that works directly with the language internals.

 
The typeOf Operator

How is it used?

Since typeof is a unary operator, the operand follows the operator. No additional punctuation is required.

typeof 2 //"number"
typeof "belladonna" //"string"

 
But it works when I call it as a function?

The typeof operator is not a function. You can surround the operand with parentheses so that the expression looks like a function call, but the parentheses will simply act as a grouping operator (second only to the comma operator in the obscurity pecking order!). In fact you can decorate the operand with all manner of punctuation without derailing the operator.

typeof (2) //"number"
typeof(2) //"number"
typeof ("a", 3) //"number"
typeof (1 + 1) //"number"

 
What does it return?

The returned value is a somewhat arbitrary representation of the operand’s type. The table below (based on the one in the ES5 spec) provides a summary:

Type of val Result
Undefined undefined
Null object
Boolean boolean
Number number
String string
Object (native and not callable) object
Object (native or host and
callable)
function
Object (host and not
callable)
Implementation-defined

 
What’s wrong with typeof?

The most glaring issue is that typeof null returns “object”. It’s simply a mistake. There’s talk of fixing it in the next version of the ECMAScript specification, although this would undoubtedly introduce backwards compatibility issues.

var a;
typeof a; //"undefined"
typeof b; //"undefined"
alert(a); //undefined
alert(b); //ReferenceError 

Other than that, typeof is just not very discriminating. When typeof is applied to any object type other than Function, it returns “object”. It does not distinguish between generic objects and the other built-in types (Array, Arguments, Date, JSON, RegExp, Math, Error, and the primitive wrapper objects Number, Boolean and String).

Oh and you’ll hear folks complaining about this…

typeof NaN //"number"

…but that’s not the fault of the typeof operator since the standard clearly states that NaN is indeed a number.
 
A Better Way?

[[Class]]

Every JavaScript object has an internal property known as [[Class]] (The ES5 spec uses the double square bracket notation to represent internal properties, i.e. abstract properties used to specify the behavior of JavaScript engines). According to ES5, [[Class]] is “a String value indicating a specification defined classification of objects”. To you and me, that means each built-in object type has a unique non-editable, standards-enforced value for its [[Class]] property. This could be really useful if only we could get at the [[Class]] property…

Object.prototype.toString

…and it turns out we can. Take a look at the ES 5 specification for Object.prototype.toString…

  1. Let O be the result of calling ToObject passing the this value as the argument.
  2. Let class be the value of the [[Class]] internal property of O.
  3. Return the String value that is the result of concatenating the three Strings "[object ", class, and "]".

In short, the default toString function of Object returns a string with the following format…

[object [[Class]]]

…where [[Class]] is the class property of the object.

Unfortunately, the specialized built-in objects mostly overwrite Object.prototype.toString with toString methods of their own…

[1,2,3].toString(); //"1, 2, 3"

(new Date).toString(); //"Sat Aug 06 2011 16:29:13 GMT-0700 (PDT)"

/a-z/.toString(); //"/a-z/"

 
…fortunately we can use the call function to force the generic toString function upon them…

Object.prototype.toString.call([1,2,3]); //"[object Array]"

Object.prototype.toString.call(new Date); //"[object Date]"

Object.prototype.toString.call(/a-z/); //"[object RegExp]"

 
Introducing the toType function

We can take this technique, add a drop of regEx, and create a tiny function – a new and improved version of the typeOf operator…

var toType = function(obj) {
  return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
}

(since an new, generic object will always use the toString function defined by Object.prototype we can safely use ({}).toString as an abbreviation for Object.prototype.toString)

Let’s try it out…

toType({a: 4}); //"object"
toType([1, 2, 3]); //"array"
(function() {console.log(toType(arguments))})(); //arguments
toType(new ReferenceError); //"error"
toType(new Date); //"date"
toType(/a-z/); //"regexp"
toType(Math); //"math"
toType(JSON); //"json"
toType(new Number(4)); //"number"
toType(new String("abc")); //"string"
toType(new Boolean(true)); //"boolean"

..and now we’ll run the same tests with the typeof operator (and try not to gloat) …

typeof {a: 4}; //"object"
typeof [1, 2, 3]; //"object"
(function() {console.log(typeof arguments)})(); //object
typeof new ReferenceError; //"object"
typeof new Date; //"object"
typeof /a-z/; //"object"
typeof Math; //"object"
typeof JSON; //"object"
typeof new Number(4); //"object"
typeof new String("abc"); //"object"
typeof new Boolean(true); //"object"

 
Compare to duck-typing

Duck-typing checks the characteristics of an object against a list of known attributes for a given type (walks like a duck, talks like a duck…). Because of the limited usefulness of the typeof operator, duck-typing is popular in JavaScript. Its also error-prone. For example the arguments object of a Function has a length property and numerically indexed elements, but it is still not an Array.

Using toType is a reliable and easy alternative to duck-typing. Reliable because it talks directly to the internal property of the object, which is set by the browser engine and is not editable; easy because its a three-word check.

Here’s an illustrative example – a snippet which defines a non-compliant JSON object. The jsonParseIt function accepts a function as its argument, which it can use to test the veracity of the JSON object before using it to parse a JSON string….

window.JSON = {parse: function() {alert("I'm not really JSON - fail!")}};

function jsonParseIt(jsonTest) { 
  if (jsonTest()) {
    return JSON.parse('{"a":2}');
  } else {
    alert("non-compliant JSON object detected!");
  }
}

Let’s run it, first with duck-typing…

jsonParseIt(function() {return JSON && (typeof JSON.parse == "function")})
//"I'm not really JSON - fail!"

…whoops!…and now with the toType test…

jsonParseIt(function() {return toType(JSON) == "json"});
//"non-compliant JSON object detected!"

Could toType reliably protect against the malevolent swapping of built-in JavaScript objects with impostors? Probably not, since the perpetrator could presumably also swap the toType function. A more secure test might call ({}).toString directly…

function() { return ({}).toString.call(JSON).indexOf("json") > -1 }

..though even this would fail if Object.prototype.toString was itself maliciously re-written. Still each additional defense helps.

Compare to instanceof

The instanceof operator tests the prototype chain of the first operand for the presence of the prototype property of the second operand (the second operand is expected to be a constructor, and a TypeError will be thrown if it is not a Function):

new Date instanceof Date; //true

[1,2,3] instanceof Array; //true

function CustomType() {};
new CustomType instanceof CustomType; //true 

 
On the face of it this seems to hold promise of a nice type-checker for built-ins, however there are at least two snags with this approach:

1. Several built in objects (Math, JSON and arguments) do not have associated constructor objects – so they cannot be type-checked with the instanceof operator.

Math instanceof Math //TypeError

2. As @kangax and others have pointed out, a window can comprise multiple frames, which means multiple global contexts and therefore multiple constructors for each type. In such an environment, a given object type is not guaranteed to be an instanceof of a given constructor….

var iFrame = document.createElement('IFRAME');
document.body.appendChild(iFrame);

var IFrameArray = window.frames[1].Array; 
var array = new IFrameArray();

array instanceof Array; //false
array instanceof IFrameArray; //true; 

 
Type-checking host objects

Host objects are browser-created objects that are not specified by the ES5 standard. All DOM elements and global functions are host objects. ES5 declines to specify a return value for typeof when applied to host objects, neither does it suggest a value for the [[Class]] property of host objects. The upshot is that cross-browser type-checking of host objects is generally not reliable:

toType(window);
//"global" (Chrome) "domwindow" (Safari) "window" (FF/IE9) "object" (IE7/IE8)

toType(document);
//"htmldocument" (Chrome/FF/Safari) "document" (IE9) "object" (IE7/IE8)

toType(document.createElement('a'));
//"htmlanchorelement" (Chrome/FF/Safari/IE) "object" (IE7/IE8)

toType(alert);
//"function" (Chrome/FF/Safari/IE9) "object" (IE7/IE8)

 
The most dependable cross-browser test for an element might be to check for the existence of a nodeType property…

function isElement(obj) {
  return obj.nodeType;
}

 
…but that’s duck-typing so there are no guarantees ;-)

Where should a toType function live?

For brevity, my examples define toType as a global function. Extending Object.prototype will get you thrown to the dragons – my preference would be to extend Object directly, which mirrors the convention established by ES5 (and prototype.js before that).

Object.toType = function(obj) {
  return ({}).toString.call(obj).match(/\s([a-z|A-Z]+)/)[1].toLowerCase();
}

 
Alternatively you might choose to add the toType function to a namespace of your own, such as util.

We could get a little cleverer (inspired by Chrome’s use of “global” for window.[[Class]]). By wrapping the function in a global module we can identify the global object too:

Object.toType = (function toType(global) {
  return function(obj) {
    if (obj === global) {
      return "global";
    }
    return ({}).toString.call(obj).match(/\s([a-z|A-Z]+)/)[1].toLowerCase();
  }
})(this)

 
Let’s try it out…

Object.toType(window); //"global" (all browsers)
Object.toType([1,2,3]); //"array" (all browsers)
Object.toType(/a-z/); //"regexp" (all browsers)
Object.toType(JSON); //"json" (all browsers)
//etc..

 
What toType does not do

The toType function cannot protect unknown types from throwing ReferenceErrors…

Object.toType(fff); //ReferenceError

More precisely it is the call to toType that throws the error, not the function itself. The only guard against that (as with calls to any function) is to practice good code hygiene…

window.fff && Object.toType(fff); 

Wrap Up

OK I’ve babbled on for far longer than I intended to – so congratulations if you made it to here, I hope you found it useful. I covered a lot of ground and probably made some mistakes – please feel free to let me know about them. Also I’d love to hear about other people’s adventures in type-checking.

Further Reading

Juriy Zaytsev (“kangax”):
‘instanceof’ considered harmful (or how to write a robust ‘isArray’)

ECMA-262 5th Edition:
The typeof operator
Object Internal Properties and Methods (for more about [[Class]])
Object.prototype.toString
The instanceof operator

Thanks also to helpful feedback from @cowboy, @jdalton, @kitcambridge and @josscrowcroft. I’ve updated the article to reflect some of their suggestions

About these ads

89 thoughts on “Fixing the JavaScript typeof operator

  1. Pingback: Fixing the JavaScript typeof operator « JavaScript, JavaScript…

  2. Thanks for this excellent write-up, featuring the clearest explanation of [[Class]] I’ve ever seen :)

    What’s the reason you’re using regular expressions instead of simply calling `.slice(8, -1)` on the `({}).toString.call(object)`?

  3. Another interesting quirk is what is the typeof of the result of typeof?
    i.e.
    typeof(document) === “object”
    typeof(typeof(document)) === “string”

    but if you use it as a chainable result, you cry:
    typeof(document).slice(2) === ERUZ!

    Which is (slightly) less unintuitive if you remove the (as you mentioned, optional) brackets:
    typeof document.slice(2) === ERUZ!

    So you need to re-wrap:
    (typeof document).slice(2) === “ject”

  4. It would seem to me that it would be wiser to return non-capitalized strings as results from the toType. This is just to keep compatibility with the typeof operator.

    So “object” rather than “Object”, “number” rather than “Number” etc.

    • Hi neenko – I’m returning the name of the internal class verbatim – which is probably a more accurate representation of object types – it’s something of a convention that object types are capitalized ( reflecting the name of the constructor, where applicable) and primitive names are lower case

      I take your point with respect to comparison with the return values of typeof

  5. I kinda dig what you’re trying to do here but I have a couple issues/tweaks.

    1) Instead of `(function(){ return obj&& obj !== this; }.call(obj))` you could do `obj && obj !== Object(obj)`.

    2) ditch #1 and the distinction between primitive & object.

    3) Rename from to anything that doesn’t contain the word `type` or `class`. Something like `kind` to avoid confusion with spec’ed terms.

    4) Think about consistency with ES 5.1:

    > 15.2.4.2 Object.prototype.toString ( )
    When the toString method is called, the following steps are taken:
    1. If the this value is undefined, return “[object Undefined]“.
    2. If the this value is null, return “[object Null]“….

    I’ve done something similar to this in my fork of waldo.js.

    • I’d ditch the distinction between primitive and object as well, as anyone can always just test obj !== Object(obj).

      Also, your Object.toType behavior is inconsistent and somewhat confusing, since it only returns the typeof for truthy primitives, but not falsy primitives.

      Object.toType(true) // “boolean”
      Object.toType(false) // “Boolean”

      • Cowboy – good call – I added the primitive checker thing as a stream of conciensce thing at the end and didn’t re-read it. Lesson learned!

  6. Unfortunately, since Object.prototype.toString is mutable (still don’t understand why!?), this whole approach is not really reliable.

    Object.prototype.toString = function(){ return “[object Foo is cool!]“; };
    toType(3); // “Foo” …. wtf!?

    • Kyle, would you ever use any library that even augments Object.prototype let alone modifies existing Object.prototype definitions? Once the latter happens – your entire app is royally hosed anyway – toType or no toType.

      Don’t forget that what I’m relying on is explicitly documented on the ECMA 5 standard!

  7. Angus, in the “What’s wrong with typeof?” section’s code example, you use “type of” instead of “typeof” twice. Also, if you want this to work in IE < 8, you'll need to explicitly check and handle null and undefined, as Object.prototype.toString(obj) either of those returns "[object Object]".

    jQuery handles this by using String(obj), but since your result is uppercased, that won't work. You're best off explicitly checking for null and undefined. I've worked up a small example here (note that I'm also caching results as well, and using .slice as an alternate, possibly faster approach – I haven't perf tested it, maybe the RegExp is actually faster):

  8. toString == Object.prototype.toString //false
    toString.call(/./) //”[xpconnect wrapped native prototype]”

    this is firefox, so you can’t relly trust on toString

  9. Pingback: Fixing the JavaScript typeof operator | Javascript | Syngu

    • Hi Joss

      Good stuff! I would remove the primitive checking line (if obj && etc.) because as @cowboy points out, boolean false will slip through the cracks – also @kitcambridge’s comment on github is valid

      And without primitive check I’ve cone round to the idea of returning lowercase results

      Can’t edit or view github properly until I fire up my laptop later today – but looking good

      Good luck!

      • Hey, thanks for the feedback, and yeah I agree about the lowercase (plus it is closer to the return of typeof, eg “undefined”).

        The pull request got closed because it doesn’t fit well with what underscore is trying to be, which seems fair – indeed maybe the need to use a better version of typeof – to such an extent that it needs to be part of a library like underscore – is a good sign that some code needs to be redesigned.

  10. Pingback: JavaScript Magazine Blog for JSMag » Blog Archive » News roundup: png.js, pouchDB, finding memory leaks in JavaScript, Page Visibility API

  11. Pingback: Web excursions: August 9, 2011 - August 12, 2011 - Brett Terpstra

  12. The grouping operator isn’t obscure at all. Calling your new function toType makes it seem like it might do some sort of casting instead of type checking.

      • I would certainly think that toString is casting ‘to string’, as with toType casting ‘to type’. But that’s just my semantics.

        I’ve just implemented this and changed it simply to ‘isType’ which to me says ‘type checking’ rather than ‘type casting’.

        But that’s just me.

        Fantastic article BTW…

  13. Pingback: Cómo obtener el tipo de datos preciso de una variable en Javascript | EtnasSoft

  14. Pingback: Scratchpad / Notizblock « mraith.wordpress.com

    • @shesek – I hope I didn’t imply duck-typing is never useful
      As for performance of duck typing, that totally depends on how many checks and what type you are using for your duck typing

  15. Hello Angus,

    Very nice post. As far as “Where should a toType function live?” you can augment the Object.prototype without breaking any for .. in loop. The way to do it is to use Object.defineProperty http://ecma262-5.com/ELS5_HTML.htm#Section_15.2.3.6 and mark the property as non-enumerable, which is the default.

    So the code would look like this:
    Object.defineProperty( Object.prototype, ‘toType’, {
    value: function( obj ) {
    return ( {} ).toString.call( obj ).match( /\s([a-z|A-Z]+)/ )[ 1 ].toLowerCase();
    }
    } );

    Or you could use the `this` in your function to use it like this: new Date().toType(); // ‘date’

    Object.defineProperty( Object.prototype, ‘toType’, {
    value: function() {
    return ( {} ).toString.call( this ).match( /\s([a-z|A-Z]+)/ )[ 1 ].toLowerCase();
    }
    } );

    • thanks Petros – yes you could make it non enumerable but there are other reasons not too extend Object.prototype

      1) Ever since about 2005 there has been an unwritten agreement amongst JS coders to not do this
      2) You are augmenting the global object which could be confusing and lead to errors

      e.g. window.toType is now an object

      To be clear I am not against augmenting other built-ins like Array and Function

  16. Pingback: Lessons From A JavaScript Code Review

  17. Pingback: Возвращаясь к проверке типа данных :: Хранитель заметок

  18. Pingback: Rounded Corners 278 — Pseudonymous /by @assaf

  19. Pingback: Avoiding The Quirks: Lessons From A JavaScript Code Review « Hanzoro5's blog

  20. I tried using the proposed function, however something very very interesting happens on older webkit build:

    var a = { a: “A” };
    alert(toType(a.b));
    in Firefox, Chrome and friends -> Undefined
    in webkit (slightly older) -> Window/DOMWindow
    I can understand that this is an error in the engine probably (otherwise how could the non existing a.b be the Window?) but typeof returns ‘undefined’ correctly.
    What I am saying is when using augmentations in code, be careful to make sure it is always working.

  21. Pingback: Skilldrick » Understanding typeof, instanceof and constructor in JavaScript

  22. Pingback: A better typeof for Javascript » Refactored scope

  23. Pingback: Exaktere Alternative für de Typeof Operator in Javascript | JavaScript Developer

  24. Pingback: Spezifische Alternative für de Typeof Operator in Javascript | JavaScript Developer

  25. First of all, thanks for the function I find it very useful.
    I did however find one problem:
    If you have the following code:
    var a;
    toType(a);

    All browsers return as expected “undefined”, except for IE 8 and under, which return “object”.
    I so no altenative than to solve this like so:

    function toType (obj) {
    return typeof obj == ‘undefined’ ? ‘undefined’ : ({}).toString.call(obj).match(/\s([a-z|A-Z]+)/)[1].toLowerCase();
    }

  26. thank you very much for your solution, that was exactly what I needed for my project. However I found a little glitch, when use the function with the new Float32Array type it returns “float” instead of “float32array”. A easy fix is to add numbers to the regex range:

    var toType = function(obj) {
    return ({}).toString.call(obj).match(/\s([a-zA-Z0-9]+)/)[1].toLowerCase();
    }

  27. I am following those code. thanks, I read your post and it was amazing , KEEP IT, great job

  28. Pingback: Fixing the JavaScript typeof operator | Farkas Máté

  29. Pingback: Learning Javascript « Sarfraz Ahmed's Blog

  30. Pingback: Checking Array-ness in Javascript | Not So Techy

  31. window.fff && Object.toType(fff); – completely wrong (check false, null…)
    for the case of global variables correct way is Object.toType(window.fff)

  32. Pingback: Lessons From A Review Of JavaScript Code |Layout to HTML

  33. Pingback: JSSpy » Fixing the javascript typeof operator

  34. Great snippet. I see myself using it from now on :)
    Quick question though : where does the shortcut ({}) come from ? I’ve never seen it before ? Care to elaborate ?
    Thanks.

    • I just paid attention to the following lines :

      “since an new, generic object will always use the toString function defined by Object.prototype we can safely use ({}).toString as an abbreviation for Object.prototype.toString”

      …and I felt silly for asking the previous question.
      Sorry for the noise.

  35. I would like to thank you for the efforts you’ve put in writing this website. I really hope to check out the same high-grade content from you later on as well. In fact, your creative writing abilities has motivated me to get my own website now ;)

  36. Well, when you say they are planning to “fix” the typeof null issue, I sure hope that they add Null.prototype to the equation so that typeof null == “object” makes sense and so someVar.toString() will only fail when someVar === undefined. And honestly I wouldn’t mind making undefined an object as well :D They would still coerce the same when doing comparisons to == null or == undefined etc, and I’d even be ok with leaving typeof undefined == “undefined” which would make the interface backwards compatible.

    Object.prototype.isNull = Object.prototype.isUndef = function() { return false; };
    Null.prototype.isNull = function() { return true; };
    Undefined.prototype.isUndef = function() { return true; };

    if (someParam.isNull()) { throw new Error() };

    Easier to check if a var is null or undef, and you could mold the approach to fit your personal preferences since some people don’t like the distinction between undefined and null, and yet others do.

  37. Pingback: Lessons From A Review Of JavaScript Code - Goodfav Howto

  38. Pingback: Javascript Oggetti e [[class]] - HTML, CSS, Javascript, AJAX

  39. Pingback: Assorted Links | Buzu’s Oficial Blog

  40. Slice is better than match here, not only in IE. To fix the kitcambridge’s problem, use :
    function getType(param){
    var type = {}.toString.call(param);
    return type.slice(type.indexOf(” “) + 1, -1);
    }

  41. Pingback: TypeScript, CRM 2011, and You - CRM Entropy – A Microsoft Dynamics CRM Blog - Microsoft Dynamics CRM - Microsoft Dynamics Community

  42. Maybe I’m missing something or it is impossible, but this only seems to work on native objects. In other words, I can’t seem to use this on custom objects.

    function Foo() {};
    toType(Foo); // object

    Is it possible to set the [[Class]] on custom objects somehow?

  43. Pingback: TypeScript, CRM 2011, and You - CRM Entropy – A Microsoft Dynamics CRM Blog - Microsoft Dynamics CRM - Microsoft Dynamics Community

  44. function toType(o) {
    if (o.toType && typeof o.toType == “function”) return o.toType();
    return ….;
    }

    //since your example uses the constructor Foo, and I’m assuming you’d also want constructed objects to play nicely as well
    Foo.prototype.toType = Foo.toType = function() { return “customFoo”; }

    toType(new Foo()); //customFoo
    toType(Foo); //customFoo

    • So, short answer is that this won’t work as-is for custom objects. There’s no way to set the internal [[Class]] property.

      That is a clever solution.

  45. function F(){};
    x = new F();
    toType(x) // will return Object

    Is that correct? So the toType method is useful for built-in types but not for user defined ones?

  46. Pingback: How do I call HTMLDocument.prototype.write?CopyQuery CopyQuery | Question & Answer Tool for your Technical Queries,CopyQuery, ejjuit, query, copyquery, copyquery.com, android doubt, ios question, sql query, sqlite query, nodejsquery, dns query, update

  47. I don’t understand the need for a “toType” function. The environments that ECMAScript runs in are many and varied, so any such function will need to allow for an extraordinary array of possible types. Generally in javascript, functions assume they’ve been passed something that will behave as expected. If not, they should throw errors so that developers fix them.

    The primary use seems to be to test overloaded parameters in functions, such as “if arg0 is a string, do x, if it’s an object, do y, if it’s an array, do z, and so on. Such testing requires a fairly simple typeof test. As soon as testing gets more complex, there may be as many drawbacks as advantages, such as where objects don’t have the right “kind” but will still behave as expected if processing continues (e.g. using slice on an arguments object).

  48. BTW, I think it’s a great article, it’s just that attempting to create general solutions that cover built–in, native and host objects and types is doomed. It will certainly work in specific cases (e.g. for instances of built–in constructors), but there will be limits to it’s general applicability.

    For better performance, I’d refactor it as:

    Object.toType = (function toType(global) {
    var toString = Object.prototype.toString;
    var re = /^.*\s(\w+).*$/;
    return function(obj) {
    if (obj === global) {
    return “global”;
    }
    return toString.call(obj).replace(re,’$1′).toLowerCase();
    }
    })(this);

  49. Pingback: Lessons From A Review Of JavaScript Code

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s