Ten Features I Like About CoffeeScript
String Interpolation
It's pretty common that you'll want to build a string based on one or more variables. With CoffeeScript, variables can cleanly be placed inside of as string:
# coffeescript
"level greater than '#{maxPowerLevel}'"
// javascript
"level greater than '" + maxPowerLevel + "'"
With well-named variables, string interpolation is easier to read and maintain. Of the three main ways to do this (the third being via some type of formatting function like sprintf
), string interpolation is by far, the best, for both simple and complex cases.
Statement Modifiers
I'll admit that statement modifiers are a pretty minor thing, but as trivial as they are, I've always found them useful for simple statements:
# coffeescript
applyChanges = (elements) ->
return if elements.length == 0
...
// javascript
applyChanges = function(elements) {
if (elements.length === 0) { return; }
...
}
Callback Handling
Whether you are doing client-side development or server-side development, callbacks and JavaScript go hand-in-hand. And, although I don't mind braces, I've always found JavaScript's syntax to be far too verbose. CoffeeScript helps in two ways. First, optional parenthesis, which I generally dislike, except when dealing with callbacks. Second, blocks via indentation. Compare:
# coffeescript
$('#data').on 'click', 'tr', (e) ->
id = $(this).data('id')
...
// javascript
$('#data').on('click, 'tr', function(e) {
id = $(this).data('id')
...
});
I find this particularly useful to help minimize the clutter caused by node.js callback soup. On the downside, it isn't great when you have a parameter after the callback, such as with setInterval
or $.get
and $.post
.
Comprehensions
Looping over arrays and hashes is pretty fundamental, and the more we can enhance the experience, the better. Even if you don't leverage comprehensions as-is, simply having a for loop that exposes the value+index of an array, or the key+value of a hash, is a nice addition.
# coffeescript
books = [
{name: 'Dune', rating: 5}
{name: "Old Man's War", rating: 4}
{name: 'Foundation', rating: 3}
]
good = (b.name for b in books when b.rating > 3)
// javascript
books = [
{name: 'Dune', rating: 5},
{name: "Old Man's War", rating: 4},
{name: 'Foundation', rating: 3}
];
good = [];
for(var i = 0; i < books.length; ++i) {
if (books[i].rating > 3) {
good.push(books[i].name);
}
}
If you'd like learn more about comprehensions, I wrote a blog post on it.
The Fat Arrow
In JavaScript, the meaning of this
isn't always intuitive. Consider this CoffeeScript code:
# coffeescript
class DataTable
constructor: (elem) ->
this.element = $(elem)
this.element.on 'click', 'tr', this.clicked
clicked: (e) ->
this.highlight()
highlight: ->
....
One might expect that this
in the clicked
method to be our DataTable
instance, but instead, it'll be the row which is clicked. This means that the call to highlight
will fail, since the html row element doesn't have a highlight
method. That clicked
exists as a method of DataTable
doesn't mean much.
To solve this, CoffeeScript lets you define a method with a fat arrow =>
. Using =>
binds the function to the expected scope:
# coffeescript
class DataTable
constructor: (elem) ->
this.element = $(elem)
this.element.on 'click', 'tr', this.clicked
clicked: (e) =>
this.highlight()
highlight: =>
....
// javsacript
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
function DataTable(element) {
this.highlight = __bind(this.highlight, this);
this.clicked = __bind(this.clicked, this);
this.element = $(element).click(this.clicked);
}
DataTable.prototype.clicked = function(e) {
this.highlight();
...
};
DataTable.prototype.highlight = function() {
...
};
Sometimes you'll want the normal JavaScript behavior (via a slim arrow), and sometimes you'll want to fat arrow behavior. In general, I find myself using slim arrows more often in client-side code, and fat arrows more often on server-side code.
@ Alias
Speaking of this
, CoffeeScript has a shortcut for it, the @
symbol. It's easy to write this off as meaningless syntactical sugar, but it is useful. First, constructor parameters prefixed with @
are converted into properties:
# coffeescript
class User
constructor: (@id) ->
// jasvascript
function User(id) {
this.id = id;
}
Beyond this though, it's a nice way to define class method:
# coffeescript
class User
constructor: (@id) ->
@findById: (id) =>
...
// javascript
function User(id) {
this.id = id;
}
User.findById = function() {};
Neither @
nor the fat arrow mean that you don't have to worry about the current meaning of this
(or its alias @
). They aren't silver bullets, which isn't to say that they don't add value.
Scoping
CoffeeScript takes care of properly scoping your variables. This means that you don't use the var
keyword and don't have to worry about having an inner variable shadow an outer one. One top of that, all CoffeeScript output is wrapped inside an anonymous functions, which means you won't leak any global objects.
If you do want to expose something globally, say like our User class above, you need to attach it to a global object, like window
. Of course, on the node.js side, you can simply export it.
Clean JavaScript Output
One of the nicest things about CoffeeScript is that it generates clean and readable JavaScript. This makes it both easy to learn and debug. In fact, one of the best ways to learn CoffeeScript is to go to the official website, click the "Try CoffeeScript" button at the top, and type away. Seeing the instant translation to JavaScript is pretty useful.
Classes
We've seen examples of it as a side effect of other features, but CoffeeScript classes are cleaner and more intuitive than their JavaScript counterparts. And the only reason I'm not showing how to inherit via extend
is because I don't want to include all the necessary JavaScript...
# coffeescript
class User
constructor: (@id) ->
@findById: (id) ->
@findByEmail: (email) ->
save: =>
// javascript
function User(id) {
this.id = id;
this.save = __bind(this.save, this);
}
User.findById = function(id) {};
User.findByEmail = function(email) {};
User.prototype.save = function() {};
Operators
In CoffeeScript, ==
and !=
translate to ===
and !==
, which you probably always want to be using. This alone removes a common pain point.
In addition to that, there's the existential operator. In its simplest form, it checks for undefined and null:
# coffeescript
return null unless n?
// javascript
if (typeof n === "undefined" || n === null) {
return null;
}
But it can also be used in chains:
# coffeescript
class Unicorn
is_rookie: ->
vampire?.kills > 0
// javascript
Unicorn.prototype.is_rookie = function() {
return (typeof vampire !== "undefined" &&
vampire !== null ?
vampire.kills : void 0) > 0;
};
Wrap Up
CoffeeScript has a number of other nice features, and it's always evolving. I understand that some people simply don't like it, which is fine, since a lot of what it offers is subjective. However, I'm baffled that people dismiss as being nothing but syntactical sugar. Almost anything in the programming world can be described as syntactical sugar which sits on top of a more fundamental concept. It's less verbose and improves readability. That's a win in my books.
Of course, as someone who believes that knowing C is critical, I agree that new developers should first learn JavaScript and then spend a day learning CoffeeScript. But that's pretty much as much of a debate as I plan on having about it.