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.

typeof2 //"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.

1

vara;

2

typeofa; //"undefined"

3

typeofb; //"undefined"

4

alert(a); //undefined

5

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…

typeofNaN //"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…

Let O be the result of calling ToObject passing the this value as the argument.

Let class be the value of the [[Class]] internal property of O.

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…

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

1

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

2

3

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

4

5

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…

1

vartoType = function(obj) {

2

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

3

}

(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…

01

toType({a: 4}); //"object"

02

toType([1, 2, 3]); //"array"

03

(function() {console.log(toType(arguments))})(); //arguments

04

toType(newReferenceError); //"error"

05

toType(newDate); //"date"

06

toType(/a-z/); //"regexp"

07

toType(Math); //"math"

08

toType(JSON); //"json"

09

toType(newNumber(4)); //"number"

10

toType(newString("abc")); //"string"

11

toType(newBoolean(true)); //"boolean"

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

01

typeof{a: 4}; //"object"

02

typeof[1, 2, 3]; //"object"

03

(function() {console.log(typeofarguments)})(); //object

04

typeofnewReferenceError; //"object"

05

typeofnewDate; //"object"

06

typeof/a-z/; //"object"

07

typeofMath; //"object"

08

typeofJSON; //"object"

09

typeofnewNumber(4); //"object"

10

typeofnewString("abc"); //"object"

11

typeofnewBoolean(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….

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):

1

newDate instanceofDate; //true

2

3

[1,2,3] instanceofArray; //true

4

5

functionCustomType() {};

6

newCustomType instanceofCustomType; //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 instanceofMath //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….

1

variFrame = document.createElement('IFRAME');

2

document.body.appendChild(iFrame);

3

4

varIFrameArray = window.frames[1].Array;

5

vararray = newIFrameArray();

6

7

array instanceofArray; //false

8

array instanceofIFrameArray; //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:

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

functionisElement(obj) {

returnobj.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).

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.