How CoffeeScript

Improved My JavaScript Skills

Jurgens du Toit / jrgns — jrgns.net / @jrgns

What I learnt

Or, a quick lesson in how NOT to code in JS

1. I suck at JavaScript Prototypes

My attempt at a JS object:


var InvoiceLine = function(number, quantity, description, line_price) {
  this.number = number || lineCount;
  this.quantity = parseFloat(quantity) || 1;
  this.description = description || '';
  this.line_price = parseFloat(line_price) || 0;
  this.amount = this.quantity * this.line_price;

  this.getAmount = function() { return this.quantity * this.line_price; }
};
          

line = new InvoiceLine();
line.line_price = formatMoney(line.line_price);
line.amount = formatMoney(line.amount);
          

In CoffeeScript, though:


class InvoiceLine extends Base
  constructor: (number, description, quantity, linePrice, currency, invoice) ->
    // ...

  getAmount: ->
    @_quantity * @_linePrice

  setInvoice: (invoice) ->
    @_invoice = invoice
    @_number = invoice.lines.length + 1
          

And compiled into JS:


var InvoiceLine,
  __hasProp = {}.hasOwnProperty,
  __extends = function(child, parent) { /* ... */ };

InvoiceLine = (function(_super) {
  __extends(InvoiceLine, _super);

  function InvoiceLine(number, description, quantity, linePrice, currency, invoice) {
    this.accessor('number', 'description', 'quantity', 'linePrice', 'invoice');
    this.readable('amount', 'currency');
    this._invoice = invoice != null ? invoice : null;
    this._quantity = quantity != null ? quantity : 1;
    // ...
  }

  InvoiceLine.prototype.getAmount = function() { return this._quantity * this._linePrice; };

  InvoiceLine.prototype.setInvoice = function(invoice) { this._invoice = invoice; return this._number = invoice.lines.length + 1; };
          

2. Build Processes are Awesome

  • Plain JS doesn't need a build process...
  • Flat file with all my classes, utilities - It's a mess

CoffeeScript requires at least one build step


// Gruntfile.js
coffee: {
  compile: {
    options: {
      join: true, bare: true
    },
    files: {
      'src/invoice.js': 'src/*.coffee'
    }
  }
},
          

Why deny yourself an awesome tool?

If you dip your toe, you might as well swim


grunt.loadNpmTasks('grunt-contrib-coffee');
grunt.loadNpmTasks('grunt-coffeelint');
grunt.loadNpmTasks('grunt-contrib-uglify');
grunt.loadNpmTasks('grunt-contrib-concat');
grunt.loadNpmTasks('grunt-contrib-compress');
grunt.loadNpmTasks('grunt-contrib-jasmine');
grunt.loadNpmTasks('grunt-contrib-clean');

// Default task(s).
grunt.registerTask('debug', ['coffeelint', 'coffee', 'concat', 'jasmine']);

grunt.registerTask('default', ['coffeelint', 'coffee', 'uglify']);

grunt.registerTask('test', ['default', 'jasmine']);

grunt.registerTask('build', ['test', 'compress']);
          

3. Source maps? Source maps!

Without Sourcemaps

Debugging in the browser is difficult

  • The code is unrecognizable
  • Is it your fault or the compilers?
  • Are the changes I'm making fixing or breaking things?

With Sourcemaps!

  • Recognizable code
  • Direct link between what's happening in the browser and your CoffeeScript
  • Supported in Chrome and Firefox, FireBUG support is coming

4. A few meta-functions go
a long way

hasProp

Caching of the function


__hasProp = {}.hasOwnProperty;
          

extends


__extends = function(child, parent) {
    // Copy all the class-level methods and properties from the parent to the child
    for (var key in parent) {
        if (__hasProp.call(parent, key))
            child[key] = parent[key];
    }
    // Set up the prototype link from the child to the parent
    function ctor() {
        this.constructor = child;
    }
    // and ensure that the instanceof operator works as expected
    ctor.prototype = parent.prototype;
    child.prototype = new ctor();
    child.__super__ = parent.prototype;
    return child;
};
          

Caching of the function

slice


__slice = [].slice;
          

args = 1 <= arguments.length ? __slice.call(arguments, 0) : [];
          

indexOf


__indexOf = [].indexOf || function(item) {
    for (var i = 0, l = this.length; i < l; i++) {
        if (i in this && this[i] === item)
            return i;
    }
    return -1;
};
          

[].indexOf only supported in IE >= 9

5. Simpler is Better? The Tradeoff

CoffeeScript...

  • Hides a lot of boilerplate
  • Less code (read information) to take in
  • Easier to look past the code and at the abstraction

But?

  • Do you want the extra complexity?
  • and the extra boilerplate?
  • what about it being only a subset of JS?

In Summary

  • If you don't know what you're doing, consider CS
  • Use build scripts, regardless
  • Use sourcemaps, regardless
  • CS does some optimisations and improvements you might forget
  • Choose your own "Simpler is Better"

Questions / Comments