CoffeeScript is a solution without the problem.
— Unknown ironic source.
CoffeeScript is awesome, until it’s totally confusing, and it’s illogical, which can lead to unexpected and subtle bugs. If you’re one of the CoffeeScript haters please skip this post; for others, I’ll share a few notes on the CS quirks that I’ve observed.
For example, let’s say we have a counter and need to assign a value of index
subtracted by one to it:
numberOfRadios = index -1
But the line above is not the same as:
numberOfRadios = index - 1
Did you notice that there’s a space after the minus sign in the second example? It took me awhile to track down this type of bug. The reason for this behavior is that the first line will be converted by the compiler to the function invocation and that is probably not what you wanted.
Try the minus one snippet on CoffeeScript.org. It outputs the following JavaScript code:
var numberOfRadios;
numberOfRadios = index(-1);
numberOfRadios = index - 1;
Another pitfall, that leads to writing despondently inconsistent code (by us developers), is caused by the fact that parentheses are optional for function calls.
This example involves if
conditions in which we want to compare the value of the expression model.get()
or the operand typeof
to some strings:
a() if @model.get 'groupType' is 'radioGroupTabs'
a() if typeof @model.get 'condition' is 'function'
CoffeeScript is treating the entire thing as an argument and the results are pathetic. Duh. ;-( Here is the native JavaScript code:
if (this.model.get('groupType' === 'radioGroupTabs')) {
a();
}
if (typeof this.model.get('condition' === 'function')) {
a();
}
Placing parens over the function still does us no good. Look at this CoffeeScript code:
a() if @model.get ('groupType') is 'radioGroupTabs'
And its JavaScript output:
if (this.model.get('groupType' === 'radioGroupTabs')) {
a();
}
The workaround involves using ()
around function calls, or flipping the order in the if
statement so that the function invocation is the last bit of code in the if
statement:
a() if (typeof @model.get 'condition') is 'function'
a() if 'function' is typeof @model.get 'condition'
a() if (@model.get 'groupType') is 'radioGroupTabs'
a() if 'radioGroupTabs' is @model.get ('groupType')
The code above compiles to what we wanted originally:
if ((typeof this.model.get('condition')) === 'function') {
a();
}
if ('function' === typeof this.model.get('condition')) {
a();
}
if ((this.model.get('groupType')) === 'radioGroupTabs') {
a();
}
if ('radioGroupTabs' === this.model.get('groupType')) {
a();
Is it all good now? Not really, because I personally think this approach leads to inconsistencies: parts of CoffeeScript code that must have parentheses, while in other places they are optional.
Try these if
conditon snippets yourself on CoffeeScript.org.
The next note explains the most common beginner’s mistake in CoffeeScript; that is to use an a
instead of an a()
for function calls. However, super
inside of a class, must be called without parentheses in order to pass the arguments to it.
Moving on to the single hash (#
) comments which are just skipped over by CoffeeScript. Again, this can lead to unexpected consequences.
For example, a straightforward if
statement has a few lines of code:
unless A
b()
blah blah
But if we comment out all of the lines inside of if
, the whole thing will fail miserably at the compilation step (at least thank you for that, CoffeeScript!):
unless A
# b()
# blah blah
Just by adding an empty else
we can mitigate the failure:
unless A
# b()
# blah blah
else
c() # the code continues here
Try this snippet yourself on CoffeeScript.org
Last but not least, there is another CoffeeScript pitfall related to jQuery events that don’t propagate when the event handler returns false
. Of course, CoffeeScript philosophy makes every function an expression (the last statement is evaluated if there is no return
statement present).
b = ()->
false # usually this result is less obvious and buried deep down in code
$a = $ 'input'
$a.click ()->
console.log 'this happens all right'
b()
$a.parent().click ()->
console.log('this never happened')
Of course, in real life the b()
function is not so obvious and it’s buried somewhere deep within Backbone and Angular model methods. And the event isn’t propagating up the DOM tree (return false
). Therefore, we’ve lost the second event handler without even realizing t. Buyers beware of this elusiveness! :-)
Try the event propagation bug yourself on CoffeeScript.org
This is the end of my list. If you know of any additional CoffeeScript idiosyncrasies, please send them my way. :-)
Further CoffeeScript reading:
A new quirk? It’s time to prepare a second post. ;-) Thanks.
This works, though:
a() if @model.get('groupType') is 'radioGroupTabs'