EchoDitto Blog

Short-circuit Evaluation in JavaScript (or, the Ternary Operator, in Binary)

Here's a peculiar-looking bit of JavaScript syntax that I've repeatedly come across in the wild:

var data = data || {};

I first came across this syntax in Drupal. For example, here's the the first line of ajax.js:

Drupal.ajax = Drupal.ajax || {};

The first time I saw this, I was pretty confused. The right side looks like it belongs in an if statement, but it clearly isn't evaluating to "true" or "false". If anything, it looks like the programmer is bored and wants to create a little uncertainty in his or her life: "Give this variable the value of Drupal.ajax, or create an empty object – BUT DON'T TELL ME WHICH."

Perhaps an easier way to understand this syntax is to expand it into it's ternary form. We could rewrite the above as follows:

var data = data ? data : {};

If you're familiar with the ternary operator (which all "clever" programmers are), this should make more sense. In case you don't, the above statement says, "If data exists, leave it unchanged. Otherwise, create it as an empty object."

The terser "binary" version accomplishes the same thing thanks to something called short-circuit evaluation. As in other languages (such as PHP), the double pipe (||) tells the interpreter to skip evaluating whatever comes after the double pipe if what comes before it evaluates to true. Here, we are using the short-circuit mechanism to assign a value to a variable, instead of the more common use case of exiting an if statement as soon as possible.

The question remains, however, why you would ever use this construction. I see two reasons:

  1. To make sure you don't overwrite a value that already exists.
  2. To avoid getting a TypeError when a property of an object is undefined.

The first case is self-explanatory, so we'll just look at the second.

Suppose we have the following function that kicks off some kind of test. It takes an object, options, which can contain two optional properties, reportSuccess and timesToRun.

function runTest(options) {
  var reportSuccess, timesToRun;
 
  if (options.reportSuccess) {
    reportSuccess = options.reportSuccess;
  }
  else {
    reportSuccess = false;
  }
 
  if (options.timesToRun) {
    timesToRun = options.timeToRun;
  }
  else {
    timesToRun = 5;
  }
 
  startTest(reportSuccess, timesToRun);
}

If we run this code without passing in the options object, we'll get a TypeError on line 4. Since we didn't pass in options, it's undefined, and we can't check a property (reportSuccess) of an undefined object.

One way to solve this is to add a check for the existence of options:

function runTest(options) {
  var reportSuccess, timesToRun;
 
  if (!options) {
    options = {};
  }
 
  if (options.reportSuccess) {
    reportSuccess = options.reportSuccess;
  }
  else {
    reportSuccess = false;
  }
 
  if (options.timesToRun) {
    timesToRun = options.timeToRun;
  }
  else {
    timesToRun = 5;
  }
 
  startTest(reportSuccess, timesToRun);
}

Doing this avoids the error. But man … we sure do have a lot of code here: 17 lines just to check for two variables! Plus, we have to remember to check for the existence of each property every time we add another option. Let's see how this looks if we re-write it taking advantage of short-circuit evaluation:

function runTest(options) {
  var reportSuccess, timesToRun;
 
  options = options || {};
 
  reportSuccess = options.reportSuccess || false;
  timesToRun    = options.timesToRun    || 5;
 
  startTest(reportSuccess, timesToRun);
}

Wow. The result is smaller, easier to maintain, and (I think) easier to read. Lining things up makes it even easier to scan the list of defaults, which come after the double pipes.

As is always the case with code that sacrifices verbosity in the name of maintainability and readability, however, you should ask yourself: am I just being clever? Am I using some obscure rock star language syntax to show off and to make life miserable for the sucker who has to work on this code next? In this case, I don't think so. The syntax is a little difficult to understand, I agree. But once you get it, it's easy enough to remember that you don't have to re-learn it every time you come across it.

To drive home my point, here's another example of when being terse can improve readability. Observe the following Bad Code (written by yours truly):

if (!empty($address)) {
    $full_address = $address;
    $address_fields = array('address2', 'city', 'state', 'zip');
      foreach ($address_fields as $field) {
        if (!empty(${"$field"})) {
          $full_address .= ', ' . ${"$field"};
        }
      }
    $data['Address'] = $full_address;
}

I don't know about you, but when I see that code on lines 5 and 6, I kind of subconsciously gloss over it. There are so many metacharacters that it looks like a regular expression – except that it's not a regular expression. Yikes. That alone should've been a clue that I was doing something wrong. But at the time I wrote this, I was too enthralled by the fact that you could have a variable variable assignment in PHP. Variable variables? So meta. How can you resist?

Later, I re-wrote this code as follows:

if (!empty($address1)) {
  $data['Address'] = implode(', ', array($address1, $city, $state, $zip, $country));
}

Life is good.

Tags: