There's a great Ruby gem called Boom by Zach Holman for managing text snippets via the command line. I've used it since it was released and have even contributed to it a few times. But after using it for a long time, I realize that I don't really need the "lists" feature – the ability to store snippets with keys two levels deep. Because of Boom's syntax, I often accidentally create lists because I can't quite remember the name of the key I'm looking for.
I decided this was a good prompt for a new program, so I created Bang. Bang is a module for Node that gives you a very simple key value store with one level of depth. I use it to store all sorts of things: articles I refer to often, simple code snippets, Imgur links to animated GIFs, and strings with Unicode characters that are a pain to type.
For those, like me, who are experimenting with Node, CoffeeScript, testing using Jasmine, and annotated source using Docco, you will want to check out the source on GitHub to see some examples of all of these.
CoffeeScript has a very elegant mechanism for creating classes. If you're new to JavaScript, you may not be aware that there are no native classes, and that CoffeeScript classes are actually syntactic sugar for JavaScript's constructor functions and object prototypes. Most people are more familiar with the syntax offered by CoffeeScript, but it's a good idea to know what's happening behind the scenes.
The prototypal model
In JavaScript, there are no classes, in the classical sense. Objects are created and inherit directly from other objects. Functions are a type of object, and when invoked with the new operator, create a new object which use the function as a constructor. This new object has a hidden link to the prototype property of the constructor function that establishes its inheritance. If you attempt to access a property on the new object that doesn't exist, JavaScript will follow the inheritance chain to the constructor's prototype and use the value it finds there.
When you create a class in CoffeeScript, you're really creating a constructor function. Each instance method in the class is actually a property of the constructor's prototype.
A simple class
Let's take a look at an example class, Bear. A bear is very simple. It has one property, name, and one method, attack. A class delcared with the class keyword creates an empty constructor function. The class definition is followed by an object defining the properties of the constructor's prototype. Each key in the object becomes a property on the prototype. The special key constructor becomes the body of the constructor function itself.
class Bear
constructor: (@name) ->
attack: ->
"#{@name} the bear attacks with his bare paws!"
oswalt = new Bear "Oswalt"
console.log oswalt.attack()
# Oswalt the bear attacks with his bare paws!
Bear defines a simple constructor which takes one argument, the bear's name, and assigns it to the name property of the instance. The attack method simply returns a string saying the bear is attacking. We instantiate a new Bear named "Oswalt" and log the results of his attack method. This outputs "Oswalt the bear attacks with his bare paws!" to the console. Let's take a look at how CoffeeScript translates this into JavaScript. (I've left out some closures and added whitespace for clarity.)
var Bear, oswalt;
function Bear(name) {
this.name = name;
}
Bear.prototype.attack = function() {
return "" + this.name + " the bear attacks with his bare paws!";
};
oswalt = new Bear("Oswalt");
console.log(oswalt.attack());
As we can see, the Bear class is really just a function named Bear. It takes one argument and assigns it to the new instance's name property. The attack method is just a function assigned to the attack property of Bear's prototype. We instantiate an object by calling new Bear and passing it the bear's name. When we attempt to access the attack property of the new object, JavaScript sees there there is no such property, and travels up the hidden link to the class's prototype, where it finds the method we want and executes it.
Inheritance
CoffeeScript's class syntax is a bit cleaner than the compiled JavaScript, but ultimately not that different. Where CoffeeScript classes really shine is in abstracting away the clunky steps required to produce classical inhertiance via the prototypal model. Let's extend Bear to see how it works.
class BearWithChainsawPaws extends Bear
attack: ->
super + " BY THE WAY, HIS PAWS ARE CHAINSAWS."
rodrigo = new BearWithChainsawHands "Rodrigo"
console.log rodrigo.attack()
# Rodrigo the bear attacks with his bare paws! BY THE WAY, HIS PAWS ARE CHAINSAWS.
This new BearWithChainsawPaws class is a child class of Bear. It overrides the attack method but does not change the constructor. Note that the new attack method uses super to call Bear's version of attack. This is very useful, because there isn't a direct equivalent of super in JavaScript, as you can see in the compiled code (again modified for clarity):
var Bear, BearWithChainsawHands, rodrigo;
var __hasProp = Object.prototype.hasOwnProperty;
var __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;
};
function Bear(name) {
this.name = name;
}
Bear.prototype.attack = function() {
return "" + this.name + " the bear attacks with his bare paws!";
};
__extends(BearWithChainsawHands, Bear);
function BearWithChainsawHands() {
BearWithChainsawHands.__super__.constructor.apply(this, arguments);
}
BearWithChainsawHands.prototype.attack = function() {
return BearWithChainsawHands.__super__.attack.apply(this, arguments) + " BY THE WAY, HIS PAWS ARE CHAINSAWS.";
};
rodrigo = new BearWithChainsawHands("Rodrigo");
console.log(rodrigo.attack());
There's a lot happening here, so let's take it a bit at a time.
The first thing to notice here is that CoffeeScript has generated some boilerplate above the classes themselves that wasn't there in the first example. __hasProp is just a shortcut for Object.prototype.hasOwnProperty. Since it's used in a loop in the following function, this is a bit more performant. It's not particularly important in understanding how inheritance works, however. The real meat is the next function.
__extends is a helper function that sets up one class to inherit from another. The for...in loop copies all the class-level methods from the parent to the child. In this particular case, our classes have only instance methods, but if we were to have defined a class property, say Bear.awesome = true, then the loop would copy true into BearWithChainsawPaws.awesome.
The second half of __extends sets up the prototype link (which contains the instance properties and methods) from the child to the parent. At its simplest, prototypal inheritance is achieved by assigning an instance of the parent to the child's prototype. __extends uses a bit of indirection to both establish this link, and to correctly assign the constructor property for all instances of the child. This property points back at the constructor function itself, and is necessary for the instanceof operator to give the desired result. The intermediate ctor method is used to for this purpose.
Lastly, a __super__ property is added to the child class, which establishes a reference to the parent's prototype. Without this, achieving super would require manually calling the parent class by name, which is error prone and not particularly maintainable. Inside BearWithChainsawHands's methods, we can see this reference to __super__ being used to call Bear's methods through the magic of apply – a method which invokes one object's method within the context of another object.
The point
While I do think CoffeeScript is pretty rad, the point of this post is to aid in understanding of what CoffeeScript is doing behind the scenes to provide its niceties. It uses good, clean JavaScript to abstract away what, to many, is an awkward approach, and in doing so, teaches a great deal about how JavaScript works.
Further reading
This post covers a lot of ground in a short span, and many parts of it could be explained in more depth. Here are some links to the MDC docs with more detail that should be helpful:
This is the third and final part of my series on ECMAScript 5. In part one, we looked at new methods for object creation and property definition. Part two focused on tamper proofing objects. I'll now provide a quick overview of the new high level array methods.
Unlike the new methods discussed in the first two parts, the methods here are all reproducible using JavaScript itself. Native implementations are simply faster and more convenient. Having a uniform API for these operations also promotes their usage, making code clearer when shared between developers.
Search methods
Array.prototype.indexOf
indexOf provides an easy way to determine whether or not an object is in an array. It returns the first index at which the item was found, or -1 if it was not found at all. Strict equality is used to determine that an item is present in the array. An optional second argument can start the search at an index other than 0.
Identical to indexOf, but returns the last index at which an item is found, if at all.
var arr = ["apple", "banana", "carrot", "apple"];
arr.lastIndexOf("apple"); // 3
Iteration methods
Array.prototype.forEach
Ever seen this before?
for (var i = 0, l = arr.length; i < l; i++) {
doSomething(arr[i], i, arr);
}
There is now a clean, high level way to do this with forEach. The method accepts a callback which will be executed once for each item in the array. The callback receives three arguments: the value of the current iteration, the index of the current iteration, and the array itself. The following is equivalent to the above:
arr.forEach(doSomething);
An optional second argument specifies the value of this within the callback.
Array.prototype.every
every checks every element in an array against a condition, by means of a passed in function. If any of the items in the array cause the function to return a falsy value, every returns false. If they all return truthy values, every returns true. Like forEach, an optional second argument supplies a value for the callback's this, and the callback itself will receive the same three arguments.
Mapping is the most common transformation. It loops through an array, running a function and creating a new array built from the return values of each iteration. map takes the same optional second argument and receives the same callback arguments as the iteration methods.
Filtering is like mapping, but creates a new array containing only the items of the original array that return a truthy value from the callback. filter takes the familiar optional argument and its callback receives the usual suspects.
reduce is used to melt the items in an array down to a single value by the operations performed in its callback function. The callback takes the usual three iteration arguments, but is prepended with an extra argument, the value of the previous iteration's result. By default, the first iteration is effectively a no-op, so the calculation begins with the array's first value as the accumulated result and the array's second value as the current iteration. This is probably best illustrated with an example:
On the first iteration, accum is 10 and value is 20. The return value of accum + value becomes accum on the next iteration of the loop.
reduce can also take an initial value for accum as a second argument. When invoked this way, the first value in the callback is the first item in the array:
reduce's sibling, which performs the same behavior and accepts the same arguments, except it iterates beginning with the rightmost item in the array and moves left. If not specified with a argument, the initial value of accum is the last value in the array, and the initial iteration value is the second to last.
ECMAScript 5 packs lots of new, useful features into JavaScript which speed up both development and performance of code. The new APIs are implented in the most recent versions of Chrome, Firefox, Safari, and (gasp!) Internet Explorer, so I encourage you to start using these new APIs today!