Disclaimer: This text is a part of the JavaScript and Node FUNdamentals: A Collection of Essential Basics ebook which is available now for free. However, upon the book’s completion it’ll be priced at $2.99. The book is 80% done as of this writing. The formats available: PDF, EPUB and Kindle. If you would like to participate in the writing process by providing your feedback and future topics, fill this short Future Topics and Feedback form.
CoffeeScript FUNdamentals: The Better JavaScript
The CoffeeScript is a language that was built on top of JavaScript. CoffeeScript has some added benefits and its code is compiled into native JavaScript for execution.
The CoffeeScript pros include: better syntax, function and class construction patterns, automatic var
insertion, comprehensions and others.
Most of these perks will be obvious once we take a look at some examples. This quick language reference can get you started with CoffeeScript:
- Semicolons, Whitespace and Parentheses
- Vars
- Conditions
- Functions
- Classes
- Arrays
- Splats
- Comprehensions
Semicolons, Whitespace and Parentheses
While in JavaScript, semicolons are redundant and optional; in CoffeeScript they are banned.
The whitespace and indentation (typically two-space) are parts of the CoffeeScript language.
Parentheses for function invocation are optional (except when there are no arguments). The same goes for curly braces for object literals. We can even next objects without curly braces:
a =
x: 1
y: -20
z: () ->
console.log a.x+a.y
a.z()
b = [
1,
2,
x: 10
y: 20
]
Translates into this JavaScript:
var a, b;
a = {
x: 1,
y: -20,
z: function() {
return console.log(a.x + a.y);
}
};
a.z();
b = [
1, 2, {
x: 10,
y: 20
}
];
As you might have noticed, the logical block’s curly braces that we use to write code for functions (i.e., {}
) are also replaced by indentation. Let’s not forget that functions are just objects in JavaScript. :-)
Vars
CoffeeScript automatically inserts var
keywords for us and prohibits manual usage of var
. For example, a
,b
, and c
variable declarations will have the var
in the JavaScript code:
a = 10
b = 'x'
c = [1,2,3]
JavaScript code:
var a, b, c;
a = 10;
b = 'x';
c = [1, 2, 3];
CoffeeScript always puts var
s at the top of the scope where this particular variable was encountered first. The scope is defined by the function or window. For example, the anonymous function d
will have e
scoped to it, because CoffeeScript first saw e
inside of the function:
a = 10
b = 'x'
c = [1,2,3]
d = () ->
e = a
console.log e
d()
JavaScript output:
var a, b, c, d;
a = 10;
b = 'x';
c = [1, 2, 3];
d = function() {
var e;
e = a;
return console.log(e);
};
d();
Conditions
Conditions are more readable by humans (English-like?) in CoffeeScript:
a = b = c = d = 1
if a is b or b isnt c and not c is d
console.log 'true'
else
console.log 'false'
var a, b, c, d;
a = b = c = d = 1;
if (a === b || b !== c && !c === d) {
console.log('true');
} else {
console.log('false');
}
So is
is ===
, isnt
is !==
, not
is !
, and
is &&
, and or
is ||
.
In CoffeeScript some fancy and arguably more readable constructions are possible:
console.log a if a is not null
if a isnt null then console.log a
if not a is null then console.log a
unless a is null then console.log a
Note: unless
is just a shortcut for if not
.
Functions
Functions in CoffeeScript are defined with arrows ()->
and and fat arrows ()=>
(more on this later):
a = (x,y) -> console.log x+y
a(10,-5)
JavaScript code:
var a;
a = function(x, y) {
return console.log(x + y);
};
a(10, -5);
Longer expressions can be on multiple lines using indentation, while the default values can be assigned right in the function signature (i.e., (name=value)
):
a = (x, y, z=15) ->
sum = x + y + z
console.log sum
a(10,-5)
var a;
a = function(x, y, z) {
var sum;
if (z == null) {
z = 15;
}
sum = x + y + z;
return console.log(sum);
};
a(10, -5);
So back to the far arrow, it does two things:
1. Defines a function
2. Binds the new function’s scope to the current value of this
Remember that this
is dynamically scoped, i.e., its meaning changes based on where it is situated in the code (what’s the scope). For example, if we have a jQuery event handler click
, we might want to use this
as the object in which we defined the handler, not as the DOM element to which the handler is bound.
For example, this CoffeeScript code will return window
object both times (that’s what we want):
console.log @
$('div').click ()=>
console.log @
The JavaScript code:
console.log(this);
$('div').click((function(_this) {
return function() {
return console.log(_this);
};
})(this));
However, with single arrows it’s back to the DOM scope for the event handler, (this might be bad if unexpected):
console.log @
$('div').click ()->
console.log @
console.log(this);
$('div').click(function() {
return console.log(this);
});
Traditionally for the snippet above, without the CoffeeScript’s far arrows, you would see workarounds like these which use interim variables like that
, self
, or _this
:
console.log(this);
var that = this;
$('div').click(function() {
return console.log(that);
});
Classes
Classes are probably the most yummiest and the most complex and confusing feature in CoffeeScript. In JavaScript classes are absent at all! We use prototypes instead, so the objects inherit from other objects. We can also use factories, i.e., the functions that create objects.
However, if a developer wants to implement a class, it could be really tricky and often requires a good understanding of pseudo-classical instantiation patterns. This is not the case with CoffeeScript, which introduces class
keyword. Inside of the class
we can use constructor
method and super
call, for the initialization logic and the invocation of the parent’s methods correspondingly.
For example, we have a parent class Vehicle
from which we extend two classes Compact
and Suv
. In these classes, we write custom move
methods with the super
call, that allows us to re-use the logic from the parent class Vehicle
.
class Vehicle
constructor: (@name) ->
move: (meters) ->
console.log @name + " moved #{meters} miles."
class Compact extends Vehicle
move: ->
console.log "Cruising..."
super 5
class Suv extends Vehicle
move: ->
console.log "Speeding..."
super 45
camry = new Compact "Camry"
caddi = new Suv "Cadillac"
camry.move()
caddi.move()
The console outputs this:
Cruising...
Camry moved 5 miles.
Speeding...
Cadillac moved 45 miles.
The JavaScript output is quite lengthy, so no wonder developers often prefer functional or other patterns:
var Compact, Suv, Vehicle, caddi, camry,
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
Vehicle = (function() {
function Vehicle(name) {
this.name = name;
}
Vehicle.prototype.move = function(meters) {
return console.log(this.name + (" moved " + meters + " miles."));
};
return Vehicle;
})();
Compact = (function(_super) {
__extends(Compact, _super);
function Compact() {
return Compact.__super__.constructor.apply(this, arguments);
}
Compact.prototype.move = function() {
console.log("Cruising...");
return Compact.__super__.move.call(this, 5);
};
return Compact;
})(Vehicle);
Suv = (function(_super) {
__extends(Suv, _super);
function Suv() {
return Suv.__super__.constructor.apply(this, arguments);
}
Suv.prototype.move = function() {
console.log("Speeding...");
return Suv.__super__.move.call(this, 45);
};
return Suv;
})(Vehicle);
camry = new Compact("Camry");
caddi = new Suv("Cadillac");
camry.move();
caddi.move();
Arrays and Slicing
Arrays in CoffeeScript can be defined just as they are in native JavaScript: arr = [1, 2, 3]
. But we can do so much more with arrays in CoffeeScript! For example, we can use a range when defining an array (useful in iterators and comprehensions) and use slice:
arr = [1..10]
slicedArr = arr[2..4]
console.log arr, slicedArr
The console outputs:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10] [3, 4, 5]
var arr, slicedArr;
arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
slicedArr = arr.slice(2, 5);
console.log(arr, slicedArr);
Trivia fact: for array declarations with 20+ items (e.g., range of [0..20]
and larger), CoffeeScript compiler will switch to the for
loop.
Splats
Splats is a better way of using a variable number of arguments and arguments
object (from native JavaScript):
a = (x...) ->
sum = 0
x.forEach (item) -> sum += item
console.log sum
a(10,-5, 15)
var a,
__slice = [].slice;
a = function() {
var sum, x;
x = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
sum = 0;
x.forEach(function(item) {
return sum += item;
});
return console.log(sum);
};
a(10, -5, 15);
Spats work with invocations too. For example, our sum
function from the previous example needs to treat the array not as a first element, but as all arguments
:
a = (x...) ->
sum = 0
x.forEach (item) -> sum += item
console.log sum
a [-5..50]...
The output is 1260
. And the JavaScript:
var a, _i, _results,
__slice = [].slice;
a = function() {
var sum, x;
x = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
sum = 0;
x.forEach(function(item) {
return sum += item;
});
return console.log(sum);
};
a.apply(null, (function() {
_results = [];
for (_i = -5; _i <= 50; _i++){ _results.push(_i); }
return _results;
}).apply(this));
Comprehensions
The last but not least topic is comprehensions. They are probably the most used feature in CoffeeScript and replace (or at least try to replace) all loops.
For example, a simple iteration over an array:
arr = [
'x',
'y',
'z'
]
for a in arr
console.log a
The console output is:
x
y
z
The compiled code:
var a, arr, _i, _len;
arr = ['x', 'y', 'z'];
for (_i = 0, _len = arr.length; _i < _len; _i++) {
a = arr[_i];
console.log(a);
}
As is the case with conditions, comprehensions might be reversed in order, e.g., console.log a for a in arr
. Then, we can get an index which will be the second parameter, e.g., console.log a, i for a, i in arr
outputs:
x 0
y 1
z 2
The when
clause acts like a filter
method; in other words, we can apply a test to the iterator:
arr = ['x', 'y', 'z']
console.log a, i for a, i in arr when a isnt 'y'
The console outputs:
x 0
z 2
To step with an increment we can use by
: evens = (x for x in [0..10] by 2)
. In addition, for iterating over objects we can use of
:
obj =
'x': 10
'y':-2
'z': 50
coordinates = for key, value of obj
"coordinate #{key} is #{value}pt"
console.log coordinates
The console output is:
["coordinate x is 10pt", "coordinate y is -2pt", "coordinate z is 50pt"]
The JavaScript code is:
var coordinates, key, obj, value;
obj = {
'x': 10,
'y': -2,
'z': 50
};
coordinates = (function() {
var _results;
_results = [];
for (key in obj) {
value = obj[key];
_results.push("coordinate " + key + " is " + value + "pt");
}
return _results;
})();
Conclusion
This CoffeeScript FUNdamentals is a concise overview that should highlight major pros of this language, which has many more useful features. We hope that classes, arrow function declaration, comprehensions, splats, and the clean syntax were enough to spark interest and lead to more exploration and experimentation with CoffeeScript.
Here’s the list of further CoffeeScirpt reading:
I have heard of CoffeeScript many times but never really looked at it. JavaScript has always been sufficient. This is the first time I am looking at CoffeeScript. It looks interesting.