CoffeeScript compiles to JavaScript and follows its idioms closely. It's quite possible to rewrite any CoffeeScript code in Javascript and it won't look drastically different. So why would you want to use CoffeeScript?
As an experienced JavaScript programmer, you might think that learning a completely new language is simply not worth the time and effort.
But ultimately, code is for programmers. The compiler doesn't care how the code looks or how clear its meaning is; either it will run or it won't. We aim to write expressive code as programmers so that we can read, reference, understand, modify, and rewrite it.
If the code is too complex or filled with needless ceremony, it will be harder to understand and maintain. CoffeeScript gives us an advantage to clarify our ideas and write more readable code.
It's a misconception to think that CoffeeScript is very different from JavaScript. There might be some drastic syntax differences here and there, but in essence, CoffeeScript was designed to polish the rough edges of JavaScript to reveal the beautiful language hidden beneath. It steers programmers towards JavaScript's so-called "good parts" and holds strong opinions of what constitutes good JavaScript.
One of the mantras of the CoffeeScript community is: "It's just JavaScript", and I have also found that the best way to truly comprehend the language is to look at how it generates its output, which is actually quite readable and understandable code.
Throughout this chapter, we'll highlight some of the differences between the two languages, often focusing on the things in JavaScript that CoffeeScript tries to improve.
In this way, I would not only like to give you an overview of the major features of the language, but also prepare you to be able to debug your CoffeeScript from its generated code once you start using it more often, as well as being able to convert existing JavaScript.
Let's start with some of the things CoffeeScript fixes in JavaScript.
One of the great things about CoffeeScript is that you tend to write much shorter and more succinct programs than you normally would in JavaScript. Some of this is because of the powerful features added to the language, but it also makes a few tweaks to the general syntax of JavaScript to transform it to something quite elegant. It does away with all the semicolons, braces, and other cruft that usually contributes to a lot of the "line noise" in JavaScript.
To illustrate this, let's look at an example. On the left-hand side of the following table is CoffeeScript; on the right-hand side is the generated JavaScript:
CoffeeScript |
JavaScript |
---|---|
fibonacci = (n) -> return 0 if n == 0 return 1 if n == 1 (fibonacci n-1) + (fibonacci n-2) alert fibonacci 10 |
var fibonacci; fibonacci = function(n) { if (n === 0) { return 0; } if (n === 1) { return 1; } return (fibonacci(n - 1)) + (fibonacci(n - 2)); }; alert(fibonacci(10)); |
To run the code examples in this chapter, you can use the great Try CoffeeScript online tool, at http://coffeescript.org. It allows you to type in CoffeeScript code, which will then display the equivalent JavaScript in a side pane. You can also run the code right from the browser (by clicking the Run button in the upper-left corner). If you prefer to get CoffeeScript running on your computer to run the samples first, skip to the next chapter and then come back once you have CoffeeScript installed. This tool is shown in the following screenshot:
At first, the two languages might appear to be quite drastically different, but hopefully as we go through the differences, you'll see that it's all still JavaScript with some small tweaks and a lot of nice syntactical sugar.
As you might have noticed, CoffeeScript does away with all the trailing semicolons at the end of a line. You can still use a semicolon if you want to put two expressions on a single line. It also does away with enclosing braces (also known as curly brackets) for code blocks such as if
statements, switch
, and the try..catch
block.
You might be wondering how the parser figures out where your code blocks start and end. The CoffeeScript compiler does this by using syntactical whitespace. This means that indentation is used for delimited code blocks instead of braces.
This is perhaps one of the most controversial features of the language. If you think about it, in almost all languages, programmers tend to already use indentation of code blocks to improve readability, so why not make it part of the syntax? This is not a new concept, and was mostly borrowed from Python. If you have any experience with significant whitespace language, you will not have any trouble with CoffeeScript indentation.
If you don't, it might take some getting used to, but it makes for code that is wonderfully readable and easy to scan, while shaving off quite a few keystrokes. I'm willing to bet that if you do take the time to get over some initial reservations you might have, you might just grow to love block indentation.
You'll see that the clause of the if
statement does not need be enclosed within parentheses. The same goes for the alert
function; you'll see that the single string parameter follows the function call without parentheses as well. In CoffeeScript, parentheses are optional in function calls with parameters, clauses for if..else
statements, as well as while
loops.
Although functions with arguments do not need parentheses, it is still a good idea to use them in cases where ambiguity might exist. The CoffeeScript community has come up with a nice idiom: wrapping the whole function call in parenthesis. The use of the alert
function in CoffeeScript is shown in the following table:
CoffeeScript |
JavaScript |
---|---|
alert square 2 * 2.5 + 1 alert (square 2 * 2.5) + 1 |
alert(square(2 * 2.5 + 1)); alert((square(2 * 2.5)) + 1); |
Functions are first class objects in JavaScript. This means that when you refer to a function without parentheses, it will return the function itself, as a value. Thus, in CoffeeScript you still need to add parentheses when calling a function with no arguments.
By making these few tweaks to the syntax of JavaScript, CoffeeScript arguably already improves the readability and succinctness of your code by a big factor, and also saves you quite a lot of keystrokes.
But it has a few other tricks up its sleeve. Most programmers who have written a fair amount of JavaScript would probably agree that one of the phrases that gets typed the most frequently would have to be the function definition function(){}
. Functions are really at the heart of JavaScript, yet not without its many warts.
The fact that you can treat functions as first class objects as well as being able to create anonymous functions is one of JavaScript's most powerful features. However, the syntax can be very awkward and make the code hard to read (especially if you start nesting functions). But CoffeeScript has a fix for this. Have a look at the following snippets:
CoffeeScript |
JavaScript |
---|---|
-> alert 'hi there!' square = (n) -> n * n |
var square; (function() { return alert('hi there!'); }); square = function(n) { return n * n; }; |
Here, we are creating two anonymous functions, the first just displays a dialog and the second will return the square of its argument. You've probably noticed the funny ->
symbol and might have figured out what it does. Yep, that is how you define a function in CoffeeScript. I have come across a couple of different names for the symbol but the most accepted term seems to be a thin arrow or just an arrow. This is as opposed to the fat arrow, which we'll discuss later.
Notice that the first function definition has no arguments and thus we can drop the parenthesis. The second function does have a single argument, which is enclosed in parenthesis, which goes in front of the ->
symbol. With what we now know, we can formulate a few simple substitution rules to convert JavaScript function declarations to CoffeeScript. They are as follows:
Replace the
function
keyword with->
If the function has no arguments, drop the parenthesis
If it has arguments, move the whole argument list with parenthesis in front of the
->
symbolMake sure that the function body is properly indented and then drop the enclosing braces
You might have noted that in both the functions, we left out the return
keyword. By default, CoffeeScript will return the last expression in your function. It will try to do this in all the paths of execution. CoffeeScript will try turning any statement (fragment of code that returns nothing) into an expression that returns a value. CoffeeScript programmers will often refer to this feature of the language by saying that everything is an expression.
This means you don't need to type return
anymore, but keep in mind that this can, in many cases, alter your code subtly, because of the fact that you will always return something. If you need to return a value from a function before the last statement, you can still use return
.
Function arguments can also take an optional default value. In the following code snippet you'll see that the optional value specified is assigned in the body of the generated Javascript:
CoffeeScript |
JavaScript |
---|---|
square = (n=1) -> alert(n * n) |
var square; square = function(n) { if (n == null) { n = 1; } return alert(n * n); }; |
In JavaScript, each function has an array-like structure called arguments
with an indexed property for each argument that was passed to the function. You can use arguments
to pass in a variable number of parameters to a function. Each parameter will be an element in arguments and thus you don't have to refer to parameters by name.
Although the arguments
object acts somewhat like an array, it is in not in fact a "real" array and lacks most of the standard array methods. Often, you'll find that arguments
doesn't provide the functionality needed to inspect and manipulate its elements like they are used with an array.
This has forced many programmers to use a hack by making Array.prototype.slice
copy the argument
object elements, or to use the jQuery.makeArray
method to create a standard array, which can then be used like normal.
CoffeeScript borrows this pattern of creating an array from arguments that are represented by splats
, denoted with three dots (...
). These are shown in the following code snippet:
CoffeeScript:
gpaScoreAverage = (scores...) -> total = scores.reduce (a, b) -> a + b total / scores.length alert gpaScoreAverage(65,78,81) scores = [78, 75, 79] alert gpaScoreAverage(scores...)
JavaScript:
var gpaScoreAverage, scores, __slice = [].slice; gpaScoreAverage = function() { var scores, total; scores = 1 <= arguments.length ? __slice.call(arguments, 0) : []; total = scores.reduce(function(a, b) { return a + b; }); return total / scores.length; }; alert(gpaScoreAverage(65, 78, 81)); scores = [78, 75, 79]; alert(gpaScoreAverage.apply(null, scores));
Notice that in the function definition, the parameter is followed by ...
. This tells CoffeeScript to allow for variable arguments. The function can then be invoked using either a list of parameters or an array followed by ...
.
In JavaScript, you create local variables by prefixing their declarations with a var
keyword. If you omit it, the variable will be created in the global scope.
You'll see throughout these examples that that we didn't need to use the var
keyword, and that CoffeeScript created the actual variable declarations at the top of the function in the generated JavaScript.
If you're an experienced JavaScripter, you might be wondering how you would then go about creating global variables. The simple answer is you can't.
Many people (probably including the authors of CoffeeScript) would argue that this is a good thing, because in most cases global variables should be avoided. Don't fret though, as there are ways to create top-level objects that we'll get to in a moment. But this does lead us neatly onto another benefit of CoffeeScript.
Take a look at the
following snippet of JavaScript. Notice that a variable called salutation
gets defined in two places, inside the function, as well as after the function gets called the first time:
JavaScript |
---|
var greet = function(){ if(typeof salutation === 'undefined') salutation = 'Hi!'; console.log(salutation); } greet(); salutation = "Bye!"; greet(); |
In JavaScript, when you omit the var
keyword while declaring a variable, it immediately becomes a global variable. Global variables are available in all scopes, and thus can be overwritten from anywhere, which often ends up as being a mess.
In the previous example, the greet
function first checks if the salutation
variable is defined (by checking if typeof
equals undefined
, a common workaround to see if a variable is defined in JavaScript). If it has not been defined previously, it creates it without a var
keyword. This will immediately promote the variable to the global scope. We can see the consequences of this in the rest of the snippet.
The first time the greet
function is called, the string Hi! will be logged. After the salutation has been changed and the function is called again, the console will instead log Bye!. Because the variable was leaked to be a global variable, its value was overwritten outside of the function scope.
This odd "feature" of the language has been the cause of many a headache for some weary programmer who forgot to include a var
keyword somewhere. Even if you mean to declare a global variable, it is generally considered to be a bad design choice, which is why CoffeeScript disallows it.
CoffeeScript will always add the var
keyword to any variable declaration to make sure that it doesn't inadvertently end up as a global declaration. In fact, you should never type var
yourself, and the compiler will complain if you do.
When you declare a var
normally at the top level of your script in JavaScript, it will still be available globally. This can also cause havoc when you include a bunch of different JavaScript files, since you might overwrite variables declared in earlier scripts.
In JavaScript and subsequently CoffeeScript, functions act as closures, meaning that they create their own variable scope as well as having their enclosing scope variables available to them.
Throughout the years, a common pattern started to emerge where library authors wrap their entire script in an anonymous closure function that they assign to a single variable.
The CoffeeScript compiler does something similar, and will wrap scripts in an anonymous function to avoid leaking its scope. In the following sample, the JavaScript is the output of running the CoffeeScript compiler:
CoffeeScript |
JavaScript |
---|---|
greet = -> salutation = 'Hi!' |
(var greet; greet = function() { var salutation; return salutation = 'Hi!'; }).call(this); |
Here you can see how CoffeeScript has wrapped the function definition in its own scope.
There are, however, certain cases where you would want a variable to be available throughout your application. Usually attaching a property to an existing global object can do this. When you're in the browser, you can just create a property on the global window
object.
In browser-side JavaScript, the window
object represents an open window. It's globally available to all other objects and thus can be used as a global namespace or container for other objects.
While we are on the subject of objects, let's talk about another part of JavaScript that CoffeeScript makes much better: defining and using objects.
The JavaScript language has a wonderful and unique object model, but the syntax and semantics for creating objects and inheriting from them has always been a bit cumbersome and widely misunderstood.
CoffeeScript cleans this up in a simple and elegant syntax that does not stray too far from idiomatic JavaScript. The following code demonstrates how CoffeeScript compiles its class syntax into JavaScript:
CoffeeScript:
class Vehicle constructor: -> drive: (km) -> alert "Drove #{km} kilometres" bus = new Vehicle() bus.drive 5
JavaScript:
var Vehicle, bus; Vehicle = (function() { function Vehicle() {} Vehicle.prototype.drive = function(km) { return alert("Drove " + km + " kilometres"); }; return Vehicle; })(); bus = new Vehicle(); bus.drive(5);
In CoffeeScript, you use the class
keyword to define object structures. Under the hood, this creates a function object with function methods added to its prototype. The constructor: operator
will create a constructor function that will be called when your object gets initialized with the new
keyword.
All the other function methods are declared using the methodName: () ->
syntax. These are created on the prototype of the object.
Note
Did you notice the #{km}
in our alert string? This is the string interpolation syntax, which was borrowed from Ruby. We'll talk about this later in the chapter.
What about object inheritance? Although it's possible, normally this is such a pain in JavaScript that most programmers don't even bother, or use a third-party library with non-standard semantics.
In this example you can see how CoffeeScript makes object inheritance elegant:
CoffeeScript:
class Car extends Vehicle constructor: -> @odometer = 0 drive: (km) -> @odometer += km super km car = new Car car.drive 5 car.drive 8 alert "Odometer is at #{car.odometer}"
JavaScript:
Car = (function(_super) { __extends(Car, _super); function Car() { this.odometer = 0; } Car.prototype.drive = function(km) { this.odometer += km; return Car.__super__.drive.call(this, km); }; return Car; })(Vehicle); car = new Car; car.drive(5); car.drive(8); alert("Odometer is at " + car.odometer);
This example does not contain all the JavaScript code that will be generated by the compiler, but has enough to highlight the interesting parts. The extends
operator is used to set up the inheritance chain between two objects and their constructors. Notice how much simpler the call to the parent class becomes with super
.
As you can see, @odometer
was translated to this.odometer
. The @
symbol is just a shortcut for this
. We'll talk about it further on in this chapter.
The class
syntax is, in my opinion, where you'll find the greatest difference between CoffeeScript and its compiled JavaScript. However, most of the time it just works and once you understand it you'll rarely have to worry about the details.
If you're an experienced JavaScript programmer who still likes to do all of this yourself, you don't need to use class
. CoffeeScript still provides the helpful shortcut to get at prototypes through the ::
symbol, which will be replaced by .prototype
in the generated JavaScript, as shown in the following code snippet:
CoffeeScript |
JavaScript |
---|---|
Vehicle::stop=-> alert'Stopped' |
Vehicle.prototype.stop(function() { return alert('Stopped'); }); |
JavaScript has lots of other small annoyances that CoffeeScript makes nicer. Let's have a look at some of these.
Often in JavaScript, you will need to make use of a reserved word, or a keyword that is used by JavaScript. This often happens with keys for literal objects as data in JavaScript, like class
or for
, which you then need to enclose in quotes. CoffeeScript will automatically quote reserved words for you, and generally you don't even need to worry about it.
CoffeeScript |
JavaScript |
---|---|
tag = type: 'label' name: 'nameLabel' for: 'name' class: 'label' |
var tag; tag = { type: 'label', name: 'nameLabel', "for": 'name', "class": 'label' }; |
Notice that we don't need the braces to create object literals and can use indentation here as well. While using this style, as long as there is only one property per line, we can drop the trailing commas too.
We can also write array literals in this way:
CoffeeScript |
JavaScript |
---|---|
dwarfs = [ "Sneezy" "Sleepy" "Dopey" "Doc" "Happy" "Bashful" "Grumpy" ] |
var dwarfs; dwarfs = ["Sneezy", "Sleepy", "Dopey", "Doc", "Happy", "Bashful", "Grumpy"]; |
These features combined make writing JSON a breeze. Compare the following samples to see the difference:
CoffeeScript:
"firstName": "John" "lastName": "Smith" "age": 25 "address": "streetAddress": "21 2nd Street" "city": "New York" "state": "NY" "postalCode": "10021" "phoneNumber": [ {"type": "home", "number": "212 555-1234"} {"type": "fax", "number": "646 555-4567"} ]
JavaScript:
({ "firstName": "John", "lastName": "Smith", "age": 25, "address": { "streetAddress": "21 2nd Street", "city": "New York", "state": "NY", "postalCode": "10021" }, "phoneNumber": [ { "type": "home", "number": "212 555-1234" }, { "type": "fax", "number": "646 555-4567" } ] });
For a language that deals with a lot of strings, JavaScript has always been pretty bad at building strings up from parts. Variables and expression values are often meant to be inserted inside a string somewhere, and this is usually done by concatenation using the +
operator. If you've ever tried concatenating a couple of variables in a string, you'll know this soon becomes burdensome and hard to read.
CoffeeScript has a built-in string interpolation syntax, which is similar to many other scripting languages, but was specifically borrowed from Ruby. This is shown in the following code snippet:
CoffeeScript |
JavaScript |
---|---|
greet = (name, time) -> "Good #{time} #{name}!" alert (greet 'Pete', 'morning') |
var greet; greet = function(name, time) { return "Good " + time + " " + name + "!"; }; alert(greet('Pete', 'morning')); |
You can write any expression within #{}
and its string value will be concatenated. Note that you can only use string interpolation in double-quoted strings, ""
. Single-quoted strings are literal and will be represented exactly how they are.
The equality operator ==
(and its inverse !=
) in JavaScript is fraught with dangers, and a lot of times doesn't do what you would expect. This is because it will first try to coerce objects of a different type to be the same before comparing them.
It's also not transitive, meaning it might return different values of true
or false
depending on if a type is on the left or right of the operator. Please refer to the following code snippet:
'' == '0' // false 0 == '' // true 0 == '0' // true false == 'false' // false false == '0' // true false == undefined // false false == null // false null == undefined // true
Because of its inconsistent and strange behavior, respected members in the JavaScript community advise avoiding it altogether and to rather use the identity operator, ===
in its place. This operator will always return false
if two objects are of a different type, which is consistent to how ==
works in many other languages.
CoffeeScript will always convert ==
to ===
and !=
to !===
, as shown in the following implementation:
CoffeeScript |
JavaScript |
---|---|
'' == '0' 0 == '' 0 == '0' false == 'false' false == '0' false == undefined false == null null == undefined |
'' === '0'; 0 === ''; 0 === '0'; false === 'false'; false === '0'; false === void 0; false === null; null === void 0; |
When you're trying to check if a variable exists and has a value (is not null
or undefined
) in JavaScript, you need to use this quirky idiom:
typeof a !== "undefined" && a !== null
CoffeeScript has a nice shortcut for this, the existential operator ?
, which will return false
unless a variable is undefined
or null
.
CoffeeScript |
JavaScript |
---|---|
broccoli = true; if carrots? && broccoli? alert 'this is healthy' |
var broccoli; broccoli = true; if ((typeof carrots !== "undefined" && carrots !== null) && (broccoli != null)) { alert('this is healthy'); } |
In this example, since the compiler already knows that broccoli is defined, the ?
operator will only check if it has a null
value, while it will check if carrots
is undefined
as well as null
.
The existential operator has a method call variant: ?.
or just the "soak", which will allow you to swallow the method calls on null
objects in a method chain, as shown here:
CoffeeScript |
JavaScript |
---|---|
street = person?.getAddress()?.street |
var street, _ref; street = typeof person !== "undefined" && person !== null ? (_ref = person.getAddress()) != null ? _ref.street : void 0 : void 0; |
If all of the values in the chain exist, you should get the expected result. If any of them should be null
or undefined
, you will get an undefined value, instead of TypeError
being thrown.
Although this is a powerful technique, it can also be easily abused and make the code hard to reason with. If you have long method chains it may become hard to know just exactly where the null or undefined value came from.
The Law of Demeter , a well-known object orientation design principle, can be used to minimize this kind of complexity and improve decoupling in your code. It can be summarized as follows:
Your method can call other methods in its class directly
Your method can call methods on its own fields directly (but not on the fields' fields)
When your method takes parameters, your method can call methods on those parameters directly
When your method creates local objects, that method can call methods on the local objects
Note
Although, this is not a "strict law" in the sense that it should never be broken, it is more analogous to the law of nature, such that the code that tends to follow it also tends to be much simpler and more loosely coupled.
Now that we have spent some time going over some of the inadequacies and annoyances of JavaScript that CoffeeScript fixes, let's dwell on some of the other powerful features that CoffeeScript adds; some borrowed from other scripting languages and some that are unique to the language.
In CoffeeScript, looping through collections works quite differently from JavaScript's imperative approach. CoffeeScript takes ideas from functional programming languages and uses list comprehensions to transform lists instead of looping through elements iteratively.
The
while
loop is still present and works more or less the same, except that it can be used as an expression, meaning it will return an array of values:
CoffeeScript:
multiplesOf = (n, times) -> times++ (n * times while times -= 1 > 0).reverse() alert (multiplesOf 5, 10)
JavaScript:
var multiplesOf; multiplesOf = function(n, times) { times++; return ((function() { var _results; _results = []; while (times -= 1 > 0) { _results.push(n * times); } return _results; })()).reverse(); }; alert(multiplesOf(5, 10));
Notice that in the previous code, the while
body goes in front of the condition. This is a common idiom in CoffeeScript if the body is of only one line. You can do the same thing with if
statements and list comprehensions.
We can improve the readability of the previous code slightly by using the until
keyword, which is basically the negation of while
, as shown here:
CoffeeScript:
multiplesOf = (n, times) -> times++ (n * times until --times == 0).reverse() alert (multiplesOf 5, 10)
JavaScript:
var multiplesOf; multiplesOf = function(n, times) { times++; return ((function() { var _results; _results = []; while (--times !== 0) { _results.push(n * times); } return _results; })()).reverse(); }; alert(multiplesOf(5, 10));
The for
statement
doesn't work like it does in JavaScript. CoffeeScript replaces it with list comprehensions, which were mostly borrowed from the Python language and also very similar to constructs that you'll find in functional languages such as Haskell. Comprehensions provide a more declarative way of filtering, transforming, and aggregating collections or performing an action for each element. The best way to illustrate them would be through some examples:
CoffeeScript:
flavors = ['chocolate', 'strawberry', 'vanilla'] alert flavor for flavor in flavors favorites = ("#{flavor}!" for flavor in flavors when flavor != 'vanilla')
JavaScript:
var favorites, flavor, flavors, _i, _len; flavors = ['chocolate', 'strawberry', 'vanilla']; for (_i = 0, _len = flavors.length; _i < _len; _i++) { flavor = flavors[_i]; alert(flavor); } favorites = (function() { var _j, _len1, _results; _results = []; for (_j = 0, _len1 = flavors.length; _j < _len1; _j++) { flavor = flavors[_j]; if (flavor !== 'vanilla') { _results.push("" + flavor + "!"); } } return _results; })();
Although they are quite simple, comprehensions have a very condensed form and do a lot in very little code. Let's break it down to its separate parts:
[action or mapping] for [selector] in [collection] when [condition] by [step]
Comprehensions are best read from right to left, starting from the in
collection. The selector
name is a temporary name that is given to each element as we iterate through the collection. The clause in front of the for
keyword describes what you want to do with the selector
name, by either calling a method with it as an argument, selecting a property or method on it, or assigning a value.
The when
and by
guard clauses are optional. They describe how the iteration should be filtered (elements will only be returned when their subsequent when
condition is true
), or which parts of the collection to select using by
followed by a number. For example, by 2
will return every evenly numbered element.
We can rewrite our multiplesOf
function by using by
and when
:
CoffeeScript:
multiplesOf = (n, times) -> multiples = (m for m in [0..n*times] by n) multiples.shift() multiples alert (multiplesOf 5, 10)
JavaScript:
var multiplesOf; multiplesOf = function(n, times) { var m, multiples; multiples = (function() { var _i, _ref, _results; _results = []; for (m = _i = 0, _ref = n * times; 0 <= _ref ? _i <= _ref : _i >= _ref; m = _i += n) { _results.push(m); } return _results; })(); multiples.shift(); return multiples; }; alert(multiplesOf(5, 10));
The [0..n*times]
syntax is CoffeeScripts's range syntax, which was borrowed from Ruby. It will create an array with all the elements between the first and last number. When the range has two dots it will be inclusive, meaning the range will contain the specified start and end element. If it has three dots (…
), it will only contain the numbers in between.
List comprehensions were one of the biggest new concepts to grasp when I started learning CoffeeScript. They are an extremely powerful feature, but it does take some time to get used to and think in comprehensions. Whenever you feel tempted to write a looping construct using the lower level while
, consider using a comprehension instead. They provide just about everything you could possibly need when working with collections, and they are extremely fast compared to built-in ECMAScript array methods, such as .map()
and .select()
.
You can use comprehensions to loop through key-value pairs in an object, using the of
keyword, as shown in the following code:
CoffeeScript:
ages = john: 25 peter: 26 joan: 23 alert "#{name} is #{age} years old" for name, age of ages
JavaScript:
var age, ages, name; ages = { john: 25, peter: 26, joan: 23 }; for (name in ages) { age = ages[name]; alert("" + name + " is " + age + " years old"); }
CoffeeScript introduces some very nice logic and conditional features, some also borrowed from other scripting languages. The unless
keyword is the inverse of the if
keyword; if
and unless
can take the postfix form, meaning statements can go at the end of the line.
CoffeeScript also provides plain English aliases for some of the logical operators. They are as follows:
is
for==
isnt
for!=
not
for!
and
for&&
or
for||
true
can also beyes
, oron
false
can beno
oroff
Putting all this together, let's look at some code to demonstrate it:
CoffeeScript:
car.switchOff() if car.ignition is on service(car) unless car.lastService() > 15000 wash(car) if car.isDirty() chargeFee(car.owner) if car.make isnt "Toyota"
JavaScript:
if (car.ignition === true) { car.switchOff(); } if (!(car.lastService() > 15000)) { service(car); } if (car.isDirty()) { wash(car); } if (car.make !== "Toyota") { chargeFee(car.owner); }
CoffeeScript allows you to easily extract parts of an array using the ..
and ...
notation. [n..m]
will select all the elements including n
and m
, whereas [n…m]
will select only the elements between n
and m
.
Both [..]
and […]
will select the whole array. These are used in the following code:
CoffeeScript |
JavaScript |
---|---|
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] alert numbers[0..3] alert numbers[4...7] alert numbers[7..] alert numbers[..] |
var numbers; numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; alert(numbers.slice(0, 4)); alert(numbers.slice(4, 7)); alert(numbers.slice(7)); alert(numbers.slice(0)); |
CoffeeScript sure loves its ellipses. They are used by splats, ranges, and array slices. Here are some quick tips on how to identify them: If the …
is next to the last argument in a function definition or a function call, it's a splat. If it's enclosed in square brackets that are not indexing an array, it's a range. If it is indexing an array, it's a slice.
Destructuring is a powerful concept that you'll find in many functional programming languages. In essence, it allows you to pull single values from complex objects. It can simply allow you to assign multiple values at once, or deal with functions that return multiple values; as shown here:
CoffeeScript:
getLocation = -> [ 'Chigaco' 'Illinois' 'USA' ] [city, state, country] = getLocation()
JavaScript:
var city, country, getLocation, state, _ref; getLocation = function() { return ['Chigaco', 'Illinois', 'USA']; }; _ref = getLocation(), city = _ref[0], state = _ref[1], country = _ref[2];
When you run this, you get three variables, city
, state
, and country
with values that were assigned from the corresponding element in the array returned by the getLocation
function.
You can use destructuring to pull out values from objects and hashes as well. There are no limits to how deeply data in the object can be nested. Here is an example of that:
CoffeeScript:
getAddress = -> address: country: 'USA' state: 'Illinois' city: 'Chicago' street: 'Rush Street' {address: {street: myStreet}} = getAddress() alert myStreet
JavaScript:
var getAddress, myStreet; getAddress = function() { return { address: { country: 'USA', state: 'Illinois', city: 'Chicago', street: 'Rush Street' } }; }; myStreet = getAddress().address.street; alert(myStreet);
In this example, the {address: {street: ---}}
part describes your pattern, basically where to find the information you need. When we put the myStreet
variable inside our pattern, we tell CoffeeScript to assign the value in that place to myStreet
. While we can use nested objects, we can also mix and match destructuring objects and arrays, as shown in the following code:
CoffeeScript:
getAddress = -> address: country: 'USA' addressLines: [ '1 Rush Street' 'Chicago' 'Illinois' ] {address: {addressLines: [street, city, state] } } = getAddress() alert street
JavaScript:
var city, getAddress, state, street, _ref; getAddress = function() { return { address: { country: 'USA', addressLines: ['1 Rush Street', 'Chicago', 'Illinois'] } }; }; _ref = getAddress().address.addressLines, street = _ref[0], city = _ref[1], state = _ref[2]; alert(street);
Here, in the previous code, we are pulling elements from the array value that we get from addressLines
and give them names.
In JavaScript, the value of
this
refers to the owner of the currently executing function, or the object that the function is a method of. Unlike in other object-oriented languages, JavaScript also has the notion that functions are not tightly bound to objects, meaning that the value of this can be changed at will (or accidently). This is a very powerful feature of the language but can also lead to confusion if used incorrectly.
In CoffeeScript, the @
symbol is a shortcut for this
. Whenever the compiler sees something like @foo
, it will replace it with this.foo
.
Although it's still possible to use this in CoffeeScript, it's generally frowned upon and more idiomatic to use @
instead.
In any JavaScript function, the value of this
is the object that the function is attached to. However, when you pass functions to other functions or reattach a function to another object, the value of this
will change. Sometimes this is what you want, but often you would like to keep the original value of this
.
For this purpose, CoffeeScript provides the =>
, or fat arrow, which will define a function but at the same time capture the value of this
, so that the function can be safely called in any context. This is especially useful when using callbacks, for instance in a jQuery event handler.
The following example will illustrate the idea:
CoffeeScript:
class Birthday prepare: (action) -> @action = action celebrate: () -> @action() class Person constructor: (name) -> @name = name @birthday = new Birthday() @birthday.prepare () => "It's #{@name}'s birthday!" michael = new Person "Michael" alert michael.birthday.celebrate()
JavaScript:
var Birthday, Person, michael; Birthday = (function() { function Birthday() {} Birthday.prototype.prepare = function(action) { return this.action = action; }; Birthday.prototype.celebrate = function() { return this.action(); }; return Birthday; })(); Person = (function() { function Person(name) { var _this = this; this.name = name; this.birthday = new Birthday(); this.birthday.prepare(function() { return "It's " + _this.name + "'s birthday!"; }); } return Person; })(); michael = new Person("Michael"); alert(michael.birthday.celebrate());
Notice that the prepare
function on the birthday
class takes an action
function as an argument, to be called when the birthday occurs. Because we're passing this function using the fat arrow, it will have its scope fixed to the Person
object. This means we can still refer to the @name
instance variable even though it doesn't exist on the Birthday
object that runs the function.
In CoffeeScript,
switch
statements take a different form, and look a lot less like JavaScript's Java-inspired syntax, and a lot more like Ruby's case
statement. You don't need to call break
to avoid falling through to the next case
condition.
They have the following form:
switch condition when … then … …. else …
Here, else
is the default case.
Like everything else in CoffeeScript, they are expressions, and this can be assigned to a value.
Let's look at an example:
CoffeeScript:
languages = switch country when 'france' then 'french' when 'england', 'usa' then 'english' when 'belgium' then ['french', 'dutch'] else 'swahili'
JavaScript:
var languages; languages = (function() { switch (country) { case 'france': return 'french'; case 'england': case 'usa': return 'english'; case 'belgium': return ['french', 'dutch']; default: return 'swahili'; } })();
CoffeeScript doesn't force you to add a default else
clause, although it is a good programming practice to always add one, just in case.
CoffeeScript borrowed chained comparisons from Python. These basically allow you to write greater than or less than comparisons like you would in mathematics, as shown here:
CoffeeScript |
JavaScript |
---|---|
age = 41 alert 'middle age' if 61 > age > 39 |
var age; age = 41; if ((61 > age && age > 39)) { alert('middle age'); } |
Most programming books start with comments, and I thought I would end with them. In CoffeeScript, single line comments start with #
. The comments do not end up in your generated output. Multiline comments start and end with ###
, and they are included in the generated JavaScript.
You can span a string over multiple lines using the """
triple quote to enclose it.
In this chapter, we started looking at CoffeeScript from JavaScript's perspective. We saw how it can help you write shorter, cleaner, and more elegant code than you normally would in JavaScript and avoid many of its pitfalls.
We came to realize that even though CoffeeScripts' syntax seems to be quite different from JavaScript, it actually maps pretty closely to its generated output.
Later on, we delved into some of CoffeeScripts' unique and wonderful additions, like list comprehensions, destructuring assignment, and its class syntax, as well as many more convenient and powerful features such as string interpolation, ranges, splats, and array slicing.
My goal in this chapter was to convince you that CoffeeScript is a superior alternative to JavaScript, and I have tried to do so by showing the differences between them. Although I have previously said "it's just JavaScript", I hope that you'll appreciate that CoffeeScript is a wonderful and modern language in its own right, with brilliant influences from other great scripting languages.
I can still write a great deal about the beauty of the language, but I feel that we have reached the point where we can dive into some real world CoffeeScript and get to appreciate it "in the wild", so to speak.
So, are you ready? Let's get started then and get CoffeeScript installed.