A Bit of Advice for the JavaScript Semicolon Haters

|

“If you’re gonna hate, hate with some consistency.” (source)

Earlier today, one of my coworkers was contributing to a cool new JavaScript lib, and complained that while he loves contributing to open source projects (who doesn’t, right?) he finds it annoying to read JavaScript code without semicolons.

Not just that, but he mentioned that he finds it amusing when, amidst hundreds of lines of otherwise pristine semicolon-less code, there is a single line that starts with a semicolon.

So I tweeted about it, then someone tweeted back and I decided to work up an example.

Then I decided to write a blog post. Although that’s mostly because I have a lot of work to do, and I’m an expert procrastinator.

The Setup

The examples on this page all utilize the following code, which consists of an object that has two properties, as well as a function that logs the value of the prop property of the passed-in object. Everything was tested in the Chrome console which utilizes the V8 JavaScript Engine and implements ES5 features like trailing commas in object and array literals as well as the Array#forEach method.

Also, despite my fondness for semicolons, I have removed all “unnecessary” semicolons from the following examples in the hopes of making this content more semicolon hater-friendly.

var obj = {
  prop: 'success',
  undefined: [{prop: 'fail'}],
}

var logProp = function(obj) {
  console.log(obj.prop)
}

logProp(obj)                 // 'success'
logProp(obj['undefined'][0]) // 'fail'

Avoiding Cognitive Overload by Avoiding ASI

As I’ve said before, I personally find having to remember all the ASI rules tiresome, so I use semicolons freely and lint all my JavaScript with JSHint via grunt, which tells me if there are extras or if some are missing.

For example, according to JavaScript’s Rules of Automatic Semicolon Insertion, a semicolon isn’t automatically inserted between the two following lines, so they are treated as one statement:

var a = obj
[a].forEach(logProp)   // 'fail'

So, what’s really happening?

// Because a semicolon isn't inserted, that code behaves like this...
var a = obj[a].forEach(logProp)

// and since `a` is still `undefined` at the time `obj[a]` is evaluated...
var a = obj[undefined].forEach(logProp)

// and object properties are coerced to String...
var a = obj['undefined'].forEach(logProp)

// the array stored in `obj['undefined']` gets iterated over...
var a = [{prop: 'fail'}].forEach(logProp)

// logging 'fail'.

A semicolon can be inserted between the two (either at the end of the first line or the beginning of the second line) to force JavaScript to treat each line as a separate statement.

var b = obj;
[b].forEach(logProp)   // 'success'

Although inserting a semicolon at the beginning of a line in this style—much like comma-first arrays, which are also entirely unnecessary in environments that support trailing commas in object and array literals—seems to be favored by semicolon haters.

var c = obj
;[c].forEach(logProp)  // 'success'

Or you can put semicolons after every statement, in which case you’ll never have any problems… but that’s not the semicolon hater “way.” And besides, the point of this article is to suggest that if you’re going to hate semicolons, you might as well go all the way and completely avoid the edge-cases where ASI doesn’t insert a semicolon for you.

Which brings me to the solution. In the following example, JavaScript is forced to treat the second line as a separate statement not by preceding it with a semicolon, but by putting it inside a block. Look ma, no semicolons, and not half-assed, either.

var d = obj
{[d].forEach(logProp)} // 'success'

I am going to go one step further however, and argue that if you desire true semicolon-less aesthetic consistency, you should put every statement inside a block. Since blocks are just statements that can contain zero or more statements and variables declared with var are scoped to functions and not blocks, you shouldn’t run into any problems.

{ function foo() { return true } }
{ var result = foo() }
{ if (result) { console.log('success') } else { console.log('fail') } }

Wow, isn’t that semicolon-less code both practical and beautiful? I knew you’d agree.

No Semicolon “for” You!

Since we’re on the topic of hating semicolons (and making code unreadable) we might as well remove those annoying for statements as well, as they each require not one, but two semicolons.

You can’t avoid semicolons in for loops…

for (var i = 0; i < 10; i++) { console.log(i); }

…but you can use while loops, which don’t use semicolons, instead.

var j = 0
while (j < 10) { console.log(j++) }

Not unreadable enough for you? Commas to the rescue!

var k = 0
while (console.log(k), ++k, k < 10) {}

Although if you like comma first, you’ll probably want to do this (using a lowercase “L” as the variable name is just an unreadability bonus):

var l = 0
while ( console.log(l)
      , ++l
      , l < 10) {}

Basically, if you’re going to make your code mostly unreadable, you might as well go all the way and make it completely unreadable.

In Summary

In case you haven’t realized, this is an educational post masquerading as satire. The rules of ASI are complicated and confusing, and the “fail” issue illustrated above is one that’s commonly encountered when coding JavaScript using a semicolon-less style without a linting tool like JSHint—despite the fact that these styles claim to have been designed to make bugs more apparent.

Also, please don’t intentionally make your code unreadable; I guarantee it will be unreadable enough without any extra help.

No semicolons were harmed in the making of this article.

Post A Comment

  • Any of these HTML tags may be used for style: a, b, i, br, p, strong, em, pre, code.
  • Multi-line JavaScript code should be wrapped in <pre class="brush:js"></pre>
    (supported syntax highlighting brushes: js, css, php, plain, bash, ruby, html, xml)
  • Use &lt; instead of < and &gt; instead of > in the examples themselves.