Automatic Type Conversion In The Real World

There are a few expressions that are commonly seen in JavaScript, but which some programming purists will tell you are never a good idea. What these expressions share is their reliance on automatic type conversion — a core feature of JavaScript which is both a strength and a weakness, depending on the circumstances and your point of view.

So in this article I’d like to look at two of these expressions in particular, and consider the circumstances in which they are — and are not — a good idea.

The first of these expressions is a simple if() condition:

if(foo)
{
}

The second is a variable assignment with a choice of possible values:

var x = foo || bar;

If the foo and bar in those examples are both boolean values, then the expressions are simple: the first condition passes if foo is true; the second expression assigns foo to x if foo is true, or assigns bar to x if not.

But what if they’re not simple booleans — what if foo is an object, a string, or undefined? What if foo and bar are different data-types? To understand how these expressions will be evaluated, we need to understand how JavaScript automatically converts between data-types.

Automatic Type Conversion

JavaScript is a "loosely typed" language, which means that whenever an operator or statement is expecting a particular data-type, JavaScript will automatically convert the data to that type. The if() statement in the first example expects a boolean value, therefore whatever you define in the brackets will be converted to a boolean. The same is true for while() and do...while() statements.

JavaScript values are often referred to as being "truthy" or "falsey", according to what the result of such a conversion would be (i.e. true or false). The simplest way to think of it is like this: a value is truthy unless it’s known to be falsey; and in fact there are only six falsey values:

  • false (of course!)
  • undefined
  • null
  • 0 (numeric zero)
  • "" (empty string)
  • NaN (Not A Number)

Notable exceptions are "0" (string zero) and all types of object — which are truthy — and this includes all primitive constructors, which means that new Boolean(false) evaluates to true! (Kinda confusing, but in practice you never need to create primitive values that way.)

Note: comparing two falsey values won’t always produce the result you might expect, for example (null != false) even though both are falsey values. There are some rather complex algorithms that determine how equality evaluations work, and it’s beyond the scope of this article to discuss them. But if you’re interested in the details, have a look at the The Abstract Equality Comparison Algorithm which is part of ECMAScript 5.1.

The Condition Shortcut

The if() example I showed you at the start converts its expression to a boolean, and since objects always evaluate to true while null evaluates to false, we can use a condition like that to test for the existence of DOM elements:

var element = document.getElementById("whatever");
if(element)
{
  //the element exists
}
else
{
  //the element doesn't exist
}

That will always work reliably when dealing with DOM elements, because the DOM specification requires that a non-existent element returns null.

However, other cases are not so clear-cut, like this example:

function doStuff(foo)
{
  if(foo)
  {
    ...
  }
}

Conditions like that are frequently used to mean "if the foo argument is defined", but there are several cases where that would fail — namely, any cases where foo is a falsey value. So if, for example, it’s boolean false or an empty string, then the conditional code would not be executed, even though foo is defined.

This is what we want instead:

function doStuff(foo)
{
  if(typeof foo != "undefined")
  {
    ...
  }
}

Arguments (and other variables) which have not been defined, have a data-type of "undefined". So we can use the typeof comparator to test the argument’s data-type, and then the condition will always pass if foo is defined at all. The if() expression is still evaluating a boolean, of course, but the boolean it’s evaluating is the result of that typeof expression.

The Assignment Shortcut

The second example I showed you at the start uses a logical operator, to determine which of two values should be assigned to a variable:

var x = foo || bar;

Logical operators do not return a boolean, but they do still expect a boolean, so the conversion and evaluation happens internally. If foo evaluates to true then the value of foo is returned, otherwise the value of bar is returned. This is immensely useful.

This expression is commonly seen in event-handling functions, where it’s used to define an event argument according to the supported model:

element.onclick = function(e)
{
  e = e || window.event;
};

So e is evaluated as a boolean, and that will be truthy (an event object) if the event-argument model is supported, or it will be falsey (undefined) if not; if it’s truthy then e is returned, or if not then window.event is returned.

The same kind of expression is also commonly used to assign event properties, finding the supported property by evaluating each possibility:

var target = e.target || e.srcElement || window;

So each of those references is evaluated in turn (from left to right), and the first to evaluate to true will be returned. The first case handles the standard model, the second is for Internet Explorer, while the third is for Internet Explorer when the event might fire on the window object (which has no srcElement property).

But expressions like this are equally prone to failure, in cases where the truthy-ness of the data isn’t known. For example, another common use-case is to define defaults for optional arguments, but this is not good:

function doStuff(foo)
{
  foo = foo || "default value";
}

Now if you know for sure that foo will always be either a string or undefined, and assuming that an empty string should be treated as undefined, then that expression is safe. But if not, it will need to be re-defined to something more precise, like this for example:

function doStuff(foo)
{
  if(typeof foo != "string")
  {
    foo = "default value";
  }
}

By testing the type against "string" we can handle multiple cases — where foo is undefined, and also where it’s mis-defined as a non-string value. In that case we also allow an empty string to be valid input, but if we wanted to exclude empty strings, we’d have to add a second condition:

function doStuff(foo)
{
  if(typeof foo != "string" || foo == "")
  {
    foo = "default value";
  }
}

There are other, surprisingly subtle cases where this can be a gotcha. For example, we might have a date function that creates a unix timestamp, unless an input timestamp is optionally defined:

function doDateStuff(timestamp)
{
  timestamp = timestamp || new Date().getTime();
}

That would fail if the input is 0 — because zero is a falsey value, but it’s also a valid timestamp.

General Principles

The general lesson to take from all this is simple — think about how type conversion will affect evaluations, and take care not to fall into the gotchas we’ve encountered. With due care and attention, you can still take advantage of automatic type conversion, to shorten conditions and logical expressions wherever it’s appropriate.

It does rather beg the question though — if we know that explicit testing using typeof is always safe, while relying on automatic type conversion sometimes isn’t — then why not just be explicit all the time? Certainly, if the only reason for preferring the shorter syntax, is that it’s quicker to type, then that’s a lazy and sloppy reason.

But the fact is, JavaScript usually runs across a public network, in situations where file size makes a difference. Smaller files are faster loading, and use less bandwidth, and little syntax shortcuts can really add-up.

Taking advantage of shorter expressions is not an optimization as such, it’s just a coding style that makes the best of language features.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • http://www.xoogu.com/ Dave

    I don’t get your example if(typeof foo != “string” && foo != “”)

    Since you are first checking that foo is not a string, why also check to see if it is not an empty string?

    • http://www.brothercake.com/ James Edwards

      That condition is to ensure that the argument must be a non-empty string. So the default condition will happen either if foo is not a string, or if foo is a string but it’s empty.

      • http://www.xoogu.com/ Dave

        If you want to set the default value for non strings or an empty string, shouldn’t that be if(typeof foo != "string" || foo == "") then?

        If you want to set the default value for anything that is not a string, you’d only need if(typeof foo != "string")

        With if(typeof foo != "string" && foo != "") the first condition will always evaluate to false with a string, so the second (and) condition will never be checked.

      • http://www.brothercake.com/ James Edwards

        Er yeah you’re right. My bad. I’ll change the example accordingly.

  • http://jokeyrhy.me/ Ron

    Funnily enough, your `typeof` checks will themselves also trigger automatic type conversion, because you use != and == instead of !== and ===

    :D

    • http://www.brothercake.com/ James Edwards

      That’s true yes, and possibly something I could have mentioned. It’s not significant because data types themselves are not ambiguous, but you’re right nonetheless.

      I know that JSLint advises to always use strict equality, but that’s not really necessary — not unless the data type might be ambiguous. Still I wonder whether the conversion implied in loose equality has an impact on performance.