Code Style: Accessing JavaScript Variables

Photo by Kelly Sikkema on Unsplash.

JavaScript's syntax allows for a lot of shortcuts when accessing variables. But shorter code can also be confusing. Which techniques are the shortest? Which are the easiest to read? A recent discussion with my co-workers led me to dive deeper.

A wise adage says, "code is read more than it is written." So my priorities are:

  1. Readability
  2. Simplicity
  3. Performance

We want a pattern that is easily recognizable and predictable so our brains don't need to process it. When code is erratic I have to actually study it to find the purpose, or conversely I make quick assumptions that could be wrong.

Our task is to access a deeply nested property -- settings.server.port -- in a safe way lest any property is undefined, then assign it to the property of another object. If settings.server.port is falsy (null, undefined, false, 0, etc.), we'll default to 80. Let's see how various methods stack up.

1. if Statements

As a baseline, let's consider how verbose we could be.

if (settings && settings.server && settings.server.port) {
	foo.bar = settings.server.port;
} else {
	foo.bar = 80;
}

This approach is recognizable by programmers from any language. But, by the same token, we don't leverage any of JavaScript's convenience syntax.

Plus, if we are quickly skimming this code it's not obvious that foo.bar gets set on both code paths; the else statement might just as easily set hasPort = false or run handleMissingPort(). Nor is it obvious that the if criteria merely check the property tree for undefined values, instead of including a property like !useDefaultPort. This pattern opens the door for unexpected logic to hide inside.

2. Self-executing Anonymous Function

bar: (function() {
        if (settings && settings.server && settings.server.port)
            return settings.server.port;
        return 80;
    }()),

By wrapping our logic in a self-executing function, it now runs as a single statement. This allows us to use it in a large object initialization block, like:

var foo = {
	bar: 0,
	baz: true,
};

We see this all the time in JavaScript, so all methods moving forward should accommodate that syntax.

We can be sure that the result of the anonymous function is assigned to the bar property, but like example #1 we would need to inspect the contents of the function to find side effects like hasPort = false or handleMissingPort(), and extra conditions like !useDefaultPort.

3. Ternary Operator

bar: (settings && settings.server && settings.server.port) ?
    settings.server.port :
    80,

Another improvement. The ternary operator's tighter syntax helps minimize the logic since only one statement can run per code path. It's possible that someone could execute an anonymous function in one of the paths, but that giant block of code would hopefully catch our eye.

Nevertheless, this format still leaves lots of room for unexpected logic. The conditional statement might still contain weird conditions and either code path might call a function with side-effects (e.g. getDefaultPortAndEmailAdmins()).

4. Conditional Shorthand

bar: (settings && settings.server && settings.server.port) || 80,

This technique is much shorter because it uses JavaScript syntax that you won't find in every language.

By comparison, PHP is also a loosely-typed language. But its conditional operators always return a Boolean.

// Conditionals in PHP
$and = ('Cat' && 'Dog');
echo $and . ': ' . gettype($and);
$or = ('Cat' || 'Dog');
echo $or . ': ' . gettype($or);
// 1: boolean// 1: boolean

When I was new to JavaScript, the results of the same code surprised me.

// Conditionals in JavaScript
var and = 'Cat' && 'Dog';
console.log(and + ': ' + typeof and);
var or = 'Cat' || 'Dog';
console.log(or + ': ' + typeof or);
// Dog: string// Cat: string

This behavior allows for succinct code because we can test and access the property at once. But beware that anyone new to JavaScript might misinterpret your code and think bar will be true or 80.

Technically there's still room for unexpected logic, same as the techniques above. But at least there is overall less code, leaving fewer opportunities for surprises.

5. try-catch Block

bar: (function() {
        try { return settings.server.port; }
        catch() { return 80; }
    }()),

This solution came from my co-worker and is smart because it's straightforward. All programmers should recognize the purpose of a try-catch block, and we've eliminated all conditional statements that could hide unexpected logic. It can also grow easily to access a property nested 5 or 10 levels deep.

On the downside, the anonymous block plus the try-catch block add a lot of boilerplate code. Because this method is uncommon in JavaScript (in my experience) it could backfire and trigger a lot of cognition to deduce what errors are being caught. And lastly try-catch adds some performance overhead in JavaScript, so avoid this technique in core parts of your app.

6. Utility Function

The last approach we'll consider is hiding all the object traversal code inside a utility function. You can write your own or import a third-party option like selectn.

bar: selectn('server.port', settings) || 80,

This option is the most succinct, and leaves zero room for side-effects or unexpected logic. It requires a slight learning curve to figure out what is happening, so you might think twice if your project is open source with many transient readers. But anyone who frequently reads the code can get used to it quickly.

Personally I would prefer a wrapper function to reverse the order of parameters and improve readability, like getProp(settings, 'server.port') || 80. selectn makes the second parameter optional with a fallback to the global scope.

UPDATE: I finally realized that my favorite library, Lodash, includes this functionality and it's everything I could ask for. See the _.get() method.

bar: _.get(settings, 'server.port') || 80,

Conclusion

Code style is inherently subjective. But if you value readability you should use a consistent technique that the reader can skim and move on without much thought. For a light library or app I like the Conditional Shorthand technique. If you're working with many nested properties, especially on an internal project, then the Utility Function technique may add a lot of value to your codebase.

Drew

Drew

Hi! I'm Drew, the Wimpy Programmer. I'm a software developer and formerly a Windows server administrator. I use this blog to share my mistakes and ideas.