Pretty image
Sure, CoffeeScript is as easy as ABC. But Trevor’s prepared to take you all the way to Z.

Like any sane programmer, I was initially wary of CoffeeScript. How could a little syntactic sugar sprinkled over JavaScript possibly justify the extra compilation step?

But after playing with CoffeeScript for just a few days, I knew I’d never go back. The syntactic sugar was only the beginning. I was writing code more quickly and with fewer bugs, because everything was so much clearer. It became much easier to tap into the good parts of JavaScript—and there are many—while steering clear of the bad. I’ve listed a few of those nasty bits below, along with some of the ways CoffeeScript makes it easy to avoid them.

1. Global Leakage

JavaScript newbies naïvely assume that var x means “I hereby declare a variable named x.” In fact, var isn’t so much a welcome mat as it is an electric fence, preventing x from roaming beyond the current scope. If you write

 x = 'Hello from the JavaScript Nation!';

and there’s no var x declaration in a surrounding scope, then x is automatically a global. Make the same mistake in two parts of your app, and one global will overwrite the other, creating an infuriatingly hard-to-isolate bug.

Even seasoned pros sometimes make this mistake, especially when chaining variable declarations: var a = 1, b = 2 declares two variables in the current scope, while var a = 1; b = 2 assigns a value to the global b.

The CoffeeScript Way

With CoffeeScript, all variables are automatically scoped. So when you write

 x = 'Welcome to CoffeeScriptville!'

you can rest assured that you aren’t messing with any globals. Not only that, but x is automatically limited to the current file thanks to an anonymous function wrapper—a wise practice in JavaScript, automatically provided by the CoffeeScript compiler.

2. this Is Something Else

There may be no more common source of confusion in JavaScript than the this keyword. You do a simple callback

 tinman.prototype.loadBrain = function() {
  this.brain = 'loading...';
  $.get('ajax/brain', function(data) { this.brain = data; });
 };

only to find the code silently failing—because this in the callback that receives the data doesn’t point to the same object that it points to outside of it.

The CoffeeScript Way

First off, the this keyword has an alias, @, to make it more visually distinct from ordinary variables that lack its curious contextual shenanigans.

Second, writing a function that’s bound to the current context—meaning that this/@ inside of the function has the same value as outside of it—is as easy as using => (the “fat arrow”) instead of the usual -> symbol to declare the function:

 tinman::loadBrain = ->
  @brain = 'loading...'
  $.get 'ajax/brain', (data) =>
  @brain = data

3. Condensed Conditionals

The ternary operator holds a special place in JavaScript: Unlike other conditional structures (if, switch), it returns a result. That means that coders face a choice between the impenetrable terseness of nested ternaries like

 closestEdge = x > width / 2 ? 'right' : x < width / 2 ? 'left' : 'center';

or a repetitive logic tree like

 if (x > width / 2) {
  closestEdge = 'right';
 } else if (x < width / 2) {
  closestEdge = 'left';
 } else {
  closestEdge = 'center';
 }

The CoffeeScript Way

All conditional statements in CoffeeScript return their result. That gives us the clarity of the second approach, without the mindless repetition:

 closestEdge =
  if x > width / 2
  'right'
  else if x < width / 2
  'left'
  else
  'center'

We could make closestEdge a function by simply adding -> after closestEdge =, since CoffeeScript functions automatically return their last expression.

4. Functional Asynchronicity

A popular test of “Do you really understand JavaScript?” goes something like this:

 for (var i = 1; i <= 3; i++) {
  setTimeout(function() { console.log(i); }, 0);
 }

The result:

 4
 4
 4

Why? Well, setTimeout—even when the timeout is set to 0—causes the given function to run only after the loop has run. And when the loop has run, i is 4 (since the loop ended when the condition i <= 3 failed). So there’s only one i, and that i is what the function passed to setTimeout sees.

To capture each value of i, you have to wrap the inside of the loop in a function and pass i to it:

 for (var i = 1; i <= 3; i++) {
  (function(i) {
  setTimeout(function() { console.log(i); }, 0);
  })(i);
 }

The CoffeeScript Way

While CoffeeScript doesn’t automatically capture the values of variables in loops (as this would break ordinary loop functionality like break, continue, and return), it does provide the do keyword to succinctly create capture functions:

 for i in [1..3]
  do (i) ->
  setTimeout (-> console.log i), 0

Now you get

 1
 2
 3

5. Repetition, Repetition

I’ll let the code speak for itself:

 x = sprite.x;
 y = sprite.y;
 css = {
  opacity: opacity,
  fontFamily: fontFamily
 };
 function(request) {
  body = request.body;
  status = request.status;
  // ...
 }

The CoffeeScript Way

Each snippet above becomes a one-liner, thanks to pattern-matching:

 {x, y} = sprite
 css = {opacity, fontFamily}
 ({body, status}) -> ...

Conclusion

CoffeeScript isn’t just about prettier code. It’s about more agile code. It’s about being more confident that you got it right the first time, while knowing that you could change it all in just a few keystrokes.

If good design patterns and rapid iteration are up your alley (and you are reading PragPub, aren’t you?), you owe it to yourself to give CoffeeScript a shot. Even if you decide it’s not for you, you’ll come away with a better understanding of JavaScript.

(Of course, if you’re upgrading to Rails 3.1, you have no choice in the matter!)

Trevor Burnham is the author of CoffeeScript: Accelerated JavaScript Development from The Pragmatic Bookshelf. He is also a full-stack web developer with a passion for new technologies, and the founder of DataBraid, a startup developing scientific computing tools that support remote collaboration. When on Twitter, he goes by @TrevorBurnham and @CoffeeScript. When offline, he’s usually in Cambridge, MA.

Send the author your feedback or discuss the article in the magazine forum.