JavaScript

Keep your JavaScript lean, linted and object-oriented.

Code Style

We follow the Airbnb JavaScript Style Guide plus JSHint to keep our syntax in check. Naming conventions not covered by linting:

  • Variables are underscored e.g. my_variable.
  • Functions and objects are camelCased e.g. myObject.
  • Classes are PascalCase e.g. MyConstructor.
  • Constants are UPPERCASED e.g, MY_CONSTRUCTOR.
  • Avoid abbreviated or deliberately short names (choose query over q).
  • If you really must, cache this into a variable named self.

Linting

We use JSHint and JSCS, so make sure you have the relevant linting frameworks installed. Copy the .jshintrc and .jscs.json files from this repository into the root of your project and restart your editor. Each file must pass linting before making it to a pull request review.

JSHint config

(.jshintrc)
// Defaults: https://github.com/jshint/jshint/blob/master/examples/.jshintrc
{
  /*
   * ENVIRONMENTS
   * =================
   */
  // Define globals exposed by modern browsers and various frameworks
  "browser": true,
  "dojo": true,
  "node": true,
  "jquery": true,
  "globals": {
    "define": true,
    "dcsMultiTrack": true,
    "$": true
  },
  /*
   * ENFORCING OPTIONS
   * =================
   */
  "immed": true,
  "eqeqeq": true,
  "eqnull": true,
  "indent": 2,
  "latedef": true,
  "newcap": true,
  "quotmark": "single",
  "trailing": true,
  "undef": true,
  "unused": true,
  "maxlen": 180,
  "strict": true
}

JSCS config

(.jscs.json)
{
  "preset": "airbnb",
  "safeContextKeyword": "self",
  "requireTrailingComma": null,
  "requireCamelCaseOrUpperCaseIdentifiers": null,
  "disallowQuotedKeysInObjects": "allButReserved",
  "disallowMultipleVarDecl": null,
  "requireMultipleVarDecl": true,
  "requireSpaceAfterBinaryOperators": [
    "=",
    ",",
    "+",
    "-",
    "/",
    "*",
    "==",
    "===",
    "!=",
    "!=="
  ],
  "requireSpaceBeforeBinaryOperators": [
    "=",
    "+",
    "-",
    "/",
    "*",
    "==",
    "===",
    "!=",
    "!=="
  ],
  "validateQuoteMarks": {
    "mark": "'",
    "escape": true
  },
  "requirePaddingNewLinesAfterBlocks": {
    "allExcept": [
      "inCallExpressions"
    ]
  },
  "requireDotNotation": {
    "allExcept": [
      "keywords"
    ]
  },
  "requireSpacesInAnonymousFunctionExpression": {
    "beforeOpeningCurlyBrace": true
  },
  "disallowSpacesInCallExpression": true,
  "disallowSpacesInAnonymousFunctionExpression": {
    "beforeOpeningRoundBrace": true
  },
  "disallowSpacesInFunctionDeclaration": {
    "beforeOpeningRoundBrace": true
  },
  "disallowSpacesInFunctionExpression": {
    "beforeOpeningRoundBrace": true
  },
  "disallowSpacesInFunction": {
    "beforeOpeningRoundBrace": true
  },
  "requireDollarBeforejQueryAssignment": false
}

Comments

Your code should be sufficiently eloquent that it doesn't need comments to describe its purpose. JSDoc-style comments are not necessary. Single-line comments above their are good, but multi-line are even better.

jQuery

jQuery is our DOM library of choice, however using it should not prescribe how you write your application. By all means use jQuery plugins where it will save time, but avoid the plugin design pattern for your own application logic.

Use the 1.x branch by default until we stop supporting IE8.

Some best practices:

  • Use on() and off() instead of bind(), unbind(), live() or click() shorthand methods.
  • Use event delegation binding to $(document) wherever possible.
  • Cache the result of selections and use find() and filter().
  • Variables that contain jQuery objects shouldn't be named with a $ prefix.
  • Best performance if selectors use #ID then find() (ie. bad $('#myid.select') good $('#myid').find('.select')). JQuery Selectors

Structure and patterns

Break down functionality into discrete modules that live outside of the global scope. Use the following patterns liberally.

MVC/MVVM

We have not chosen a single MVC framework that we use on every project, although we have each tried Backbone, Angular and Ember. Eventually one favourite might appear, but until this time you should write your JavaScript in as modular a fashion is possible.

Module pattern

The "module pattern" is a convenient way to structure singular objects, in its simplest form an object literal:

var person = {
  name: null,
  sayName: function() {
    console.log('My name is ' + this.name);
  }
};
person.name = 'Nick';
person.sayName();

The "revealing module pattern" will provide you with public/private properties, only exposing what you need:

var person = (function() {
  'use strict';

  var _name;

  var setName = function(name) {
    _name = name;
  };

  var sayName = function() {
    console.log('My name is ' + _name);
  };

  var init = function() {
    // set up the object
  };

  init();

  return {
    setName: setName,
    sayName: sayName
  };

})();

person.setName('Nick');
person.sayName();

Constructor functions

As soon as you need more than one instance of an object, consider constructor functions and the prototype:

var Person = function(name) {
  'use strict';

  this.name = name;
};

Person.prototype.sayName = function() {
  console.log('My name is ' + this.name);
}

var tom = new Person('Tom');
var dick = new Person('Dick');
var harry = new Person('Harry');

tom.sayName();
dick.sayName();
harry.sayName();

This pattern allows each object to share the same methods (defined on the prototype) instead of having them defined per instance. You lose the privacy offered by the revealing module, but the performance benefits should win.

Observer pattern

Use the observer pattern to keep your modules loosely-coupled. The easiest implementation is the publish/subscribe pattern.

Mediator pattern

While not pure, the controller above is acting as an "event aggregator". It might be expanded to be the mediator of publish/subscribe events between many models and views. An object literal might be sufficient to perform this function.

Namespacing

Choose a suitable namespace for your application:

var app = {};
app.stuff = (function() {
  //...
})();

Build systems

We use Grunt and Gulp to perform repetitive tasks like minification, compilation, unit testing and linting.

Testing tools

We use the follow tools to aid with application testing:

  • Mocha is a feature-rich JavaScript test framework running on Node.js and the browser, making asynchronous testing simple and fun.
  • Chai is a BDD/TDD assertion library for Node.js and the browser that can be delightfully paired with any JavaScript testing framework.
  • Sinon for dependency-less standalone test spies, stubs and mocks.

Example asynchronous code (Mocha)

describe('User', function(){
  describe('#save()', function(){
    it('should save without error', function(done){
      var user = new User('Luna');
      user.save(function(err){
        if (err) throw err;
        done();
      });
    });
  });
});

Example expect style (Chai)

var expect = chai.expect;

expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(foo).to.have.length(3);
expect(tea).to.have.property('flavors').with.length(3);

Spies, Stubs, mocks (Sinon)

Some goodies when integrating Sinon with Chai.

Spies

describe('User', function(){

 beforeEach(function(){
    var spy = sinon.spy(User.prototype, 'save');
    var user = new User();
 });
 afterEach(function(){
    User.prototype.init.restore();
 });

 it(".save", function () {
   user.save();
   expect(spy).have.been.calledOnce;
 });

});

Stubs

describe('User', function(){

 var user;

 beforeEach(function(){
    user = new User()
    sinon.stub(User, 'save', function(){
     return true;
    });
 });

 it(".save", function () {
   expect(user.save()).to.be.true
 });
});

CoffeeScript

Although a lot of our projects use Rails, we do not use CoffeeScript. For now it's an unnecessary layer of abstraction that has caused problems with sharing code with both our own engineers and our clients. It is easier to hire for native JavaScript engineers, especially contractors at short notice.

Performance

Some obvious performance gotchas. For more, see the full Performance guide.

  • Cache DOM queries—only select an element once.
  • Use event delegation as much as possible to reduce the number of events bound to the page.
  • Always throttle or debounce events bound to resize or scroll.
  • Understand when you cause an element repaint and reduce inline style manipulations accordingly. Batch them up or consider using CSS instead.

Useful libraries

Are you sure you need that additional library? Things to look out for when adding additional code:

  • Scope. Are you going to use all of it? If not, can you simply lift methods from it (e.g. underscore).
  • Weight. Does the benefit of having the code outweigh the extra page weight?
  • Support. Does it work in old-IEs? Is it actively developed?
  • Source. Can you get the un-minified source?

Always include the un-minified original source code in your application, with any licence attributions intact. Use a build script to minify.

Some useful libraries we use regularly:

Biography