All about types in Javascript - Type detection

Matthias Reuter's picture

This is the fourth (and last) part of a series "All about types" in Javascript.

  1. The basics - types and general rules of conversion
  2. Automatic type conversion
  3. Explicit type conversion
  4. Type detection

The drawback of having a loosely typed language like Javascript, is that you sometimes have to determine the current type of a variable. This mostly occurs when you create a function that accepts different types of parameters - or is limited to one type.

A while ago, I had a function to open up a dialog to allow the user to send an email. That function accepted a parameter recipient to prefill the to-field. For convenience, I accepted a string and an array as that parameter. If an array was given, I had to convert it to a string. So I needed to check, if I really had been given an array.

Unfortunately, detecting an array is the most complex task of type detection. So I will postpone my solution until later and start with the easier ones.

Basic type detection

Of the five types null, undefined, boolean, number and string, four are very easily detected by using the typeof operator.

typeof "some text"; // "string"
typeof 42;          // "number"
typeof true;        // "boolean"
typeof undefined;   // "undefined"

Detecting an object is similar:

typeof {};    // "object"

Detecting null

Now the problems begin. Some special values require special handling. Take null, for example. The type of null unfortunately is not "null", it is "object":

typeof null;   // "object"

To test if a variable is null, you have to compare it to null:

obj === null;  // true, if obj is null, otherwise false

Detecting NaN

The second issue comes with NaN. Its type is "number", although NaN is Not A Number. Funny, isn't it? Comparing to NaN does not help either:

NaN === NaN;  // false

I have absolutely no idea, why the language specification states to ECMAScript code, all NaN values are indistinguishable from each other.. Fortunately, there is a solution. There is a global function called isNaN that returns true, if the supplied argument is NaN and false otherwise.

isNaN(NaN);  // true
isNaN(42);   // false

An object is an object is an object

Yes, at least for type detection. It does not matter if your object is an array, a date, an HTML-Element or anything else, typeof always returns "object".

typeof new Date();  // "object"
typeof [];          // "object"
typeof {};          // "object"

Let me come back to my introductive function. I had to test, if a parameter was an array or a string. Since we don't have a solution for detecting arrays, my first idea was circumvent the problem

if (typeof recipient !== "string") {
  recipient = recipient.join(",");
}

but that was a bad idea. It lacks robustness, since my function would throw an error, if recipient was e.g. a number. Numbers don't have a join method.

So I really had to detect the type. As an advanced developper, I knew of two ways: duck-typing and the use of the instanceof operator.

Duck typing

What's duck typing? "If it walks like a duck and quacks like a duck, I would call it a duck." (attributed to James Whitcomb Riley). In other words, if the object has a join method, a length and a sort method, call it an array.

In my case, I did this:

function sendMessage (recipient) {
  if (recipient && recipient.join) {
    recipient = recipient.join(",");
  }
}

I only needed to know if the object had a property join, since this was the method I needed to call. That may differ in other situations. Maybe you need to check for sort or splice (both suggesting strongly the object is an array), maybe you only need a length property, in which case your function might also be applicable to strings.

The instanceof operator

Another way to detect the kind of an object is the instanceof operator. It returns true, if the left-hand operand is an instance of the right-hand operand:

[] instanceof Array;        // true
[] instanceof Object;       // yes, true as well
new Date() instanceof Date; // true

Therefore I could have written

function sendMessage (recipient) {
  if (recipient instanceof Array) {
    recipient = recipient.join(",");
  }
}

and mostly, that works fine. Not in my situation. The stated method is called from within an iframe, and that means from a different context. Each window has its own global object, and Array is a constructor property of the global object.

Thus Array in recipient instanceof Array refers to the array constructor of the outer window, while recipient is an instance of the array constructor of the iframe's window.

Detecting the kind of an object using Object.prototype.toString

That was when I discovered a third way to detect the kind of an object. kangax wrote about using the Object.prototype.toString method.

In summary, if Object.prototype.toString is called on an object, it results in a string like "[object Object]", where Object refers to the Class of the object ("Class" is what I called "kind"). So, calling Object.prototype.toString on an array, results in "[object Array]":

Object.prototype.toString.call([]) === "[object Array]";  // true

Please note that you have to use the call method of the toString method. The following does not work:

Object.prototype.toString([]) === "[object Object]";

This approach works with Date, RegExp and other constructors, too.

Detecting functions

One exception to the "an object is an object" mantra are functions. Though functions in javascript are objects as well, the type of a function is "function". So my solution of duck-typing would better be written as this:

function sendMessage (recipient) {
  if (recipient && typeof recipient.join === "function") {
    recipient = recipient.join(",");
  }
}

Summary

There is no consistent way to detect the type of a variable or the kind of an object. That's why many multi-purpose-libraries offer a function to wrap type detection.

By the way, in the meantime I found another solution to my problem by circumventing type detection:

function sendMessage (recipient) {
  recipient = recipient ? recipient.toString() : "";
}

If recipient is an array, the toString method returns a string containing the values, seperated by a comma, which is exactly that I needed.

That concludes my series about types in Javascript.

Comments

Anonymous's picture

Great article.
However, you can use the following for array dections too.
toString.call(obj) === "[object Array]";

Larry Batlte

Matthias Reuter's picture

This might fail.

By calling toString stand-alone instead of on an object, the execution context is searched for a toString function. If you have not defined a toString method anywhere in the execution context, that eventually leads to the global object. If the global object has a prototype and if that prototype is Object.prototype, then the correct toString method is found. But these are strong assumptions. The global object is a host object and therefore may internally work as the manufacturer of the ECMA-environment chose to implement. Especially, the global object does not have to be an instance of Object.

So a stable way to detect Arrays is as mentioned Object.prototype.toString.call([]) === "[object Array]";

jsonx's picture

Thanks for good and clear explanations.

Alabama weddings - Alabama birmingham - Wedding chapel in bi's picture

[...] All about types in Javascript - Type detection | united-coders.com "If it walks like a duck and quacks like a duck, I would call it a duck.. toString.call(obj) === "[object Array]";. Larry Batlte.www.united-coders.com/.../all-about-types-in-javascript-type-detection - All about types in Javascript - Type detection | united-coders.com [...]

Timo Frenay's picture

Please note that isNaN() is a misnomer, as it doesn't actually report whether its argument is NaN, but rather if its argument when converted to a number yields NaN. Hence: isNaN('a string') == true. So the proper way to check for NaN explicitly is: typeof(val) == 'number' && isNaN(val).