Skip to content

JavaScript

3rd-Eden edited this page Jul 19, 2012 · 10 revisions
Clone this wiki locally

JavaScript style and coding guidelines for Hotels.nl Front-End code:

Table of contents

Documenting

All the functions should be documented using the JDoc syntax. We currently don't use the full range of available tags. Those that we do use to comment our code are currently limited to:

  • {@const} for constants
  • {@API} for visibility for the functions should always be last in your documentation block.
  • {@param} for arguments
  • {@type} for type definitions
  • {@returns} type indication for the returned value, should be added above the {@api} block.
  • {@see} referencing
  • {@constructor} function constructors, should be the first item in your documentation block.

For license headers we allow the following tags:

  • {@author} File author, should include Hotels.nl
  • {@copyright}, File copyright
  • {@license}, Usage license
  • {@version}, File versioning

When you specify a documentation comment it should be a multi-line comment, starting with 2 stars /**. The description should end with a period .. A new line is added below the description when tags are added to the block. Once you are done with the documentation, another new line is added below the comment for readability.

Example

/**
 * My foo prototype stuff.
 *
 * @constructor
 * @param {String} bar
 * @api public
 */

function Foo (bar) {
  this.bar = bar;
}

/**
 * Barmizwa
 *
 * @returns {String} baz
 * @api private
 */

Foo.prototype.baritzwa = function baz () {
  return this.bar + 'mitzwa';
};

basics

The following base rules apply when working with JavaScript:

  • Indent with spaces, not tabs
  • Do not mix spaces with tabs, not even when working with third party content.
  • Do create lines longer than 80 columns, with an optional overlap to 90 for edge cases.
  • All code should be wrapped in an anonymous closure.
  • Don't introduce global variables.
  • Code should be written under Application namespace.
  • HTML code does not belong in JavaScript files and should be placed in HTML and read out with JavaScript.
  • Use single quotes instead of double quotes.
  • Variables, objects, keys etc should be written in camelCase

Linting

All the JavaScript you write should be tested using a Linter, we currently use jshint/jshint for our checks to ensure that most of your JavaScript is up to par with our standards and that most common issues are caught before testing and deployment.

If you are using VIM (you really should) you can install the scrooloose/syntastic for syntax checking.

You should then install jshint globally on your system, this is done using Node.js's Package Manager npm install jshint -g and then update your .vimrc with: let g:syntastic_javascript_checker = 'jshint'.

Variable definitions

Variables

Variables should always be specified using var, multiple variable declarations should use one single var keyword identifier and the rest of the variables should be using comma-first. The comma should start at the r character of the var keyword.

The semicolon should be placed behind the last specified variable and not a single line below it.

If you do need to expose a global, make sure you do it explicitly by prefixing the variable with the name window instead of creating an inexplicit global.

Example

// multiple variables
var foo = 'bar'
  , bar = 'baz';

// single variable
var baz = 'foobar';

// wrong
var foo = 'bar';
var bar = 'baz';
var foobar = 'foobar'
  , barfoo = 'barfoo'
  ;

Reasoning

When you fail to specify a var, the variable gets placed in the global context, potentially clobbering existing values. Also, if there's no declaration it's hard to tell which scope the variable lives in. In addition to this, there is also a performance implication when using global variables and there for it should be reduced to a minimum.

Constants

If you want to specify constants's in your code use the @const JSDoc comments to clarify this. The short version preferred over the longer @constant See JSDOC/constant for the related documentation.

Constants can be writing in full UPPERCASE to make it clear that you are dealing with a constant in your code.

Example

/**
 * The worlds most amazing line break
 *
 * @const
 * @type {String}
 */

var LINEBREAK = '\n\r';

Reasoning

There are some JavaScript engine's that support the const keyword to generate true JavaScript constants it should not be used as there performance implications. As JIT (Just In Time) compilers are not optimized for this usage because there isn't to much adoption for it.

First

Variables, Array and Objects

When specifying variables, arrays and objects comma first should be use for indenting. 2 spaces should be used for indenting.

The first item of an Array and Object should be started with 4 spaces so the next item aligns with the first item.

Reasoning

JavaScript files are known to break in certain engines when a comma is missing or if an extra comma is used. By using the comma-first code style you can easily spot the missing or extra comma's.

Example

[
    'hello'
  , 'pewpew'
];

{
    key: 'value'
  , foo: 'bar'
}

For an example of indenting with variables, see the variables section.

Operator first

Use operator first when writing a long if-statement that does not fit on one single line.

When you if statement spans more than one line, make sure that the closing ) { starts at the same level as if (.

Example

if (thisIsAReallyLongStatement === 10
  && foo == bar
  && toys.length > 0
  || shizzle === 'mynizzle'
) {
 .. code ..
}

Ternary operator first

Use ternary operator first in ternary statement if a statement does not fit on one single line. The operator should be indented with 2 spaces.

example
var x = foo ? 'bar' : 'baz';

return sack === 'hairycowsack' && sackhair.length === 80
  ? 'foo'
  : 'bar';

use strict

Each file should have a "use strict"; statement and should be tested against a strict enabled browser engine. The strict mode should either be applied to the whole file or if the code is wrapped in a closure, to that specific wrapping closure.

Strict mode support was added in JavaScript 1.8.

Reasoning

Strict mode makes changes to the semantics to avoid common JavaScript pitfalls.

Example

"use strict";

function foo () {};

Or

(function wrap () {
  "use strict";

  function foo () {};
}())

Looping

while()

A negative while loop is currently one if the fastest loops supported by different JavaScript engines. If the order of your loop doesn't matter then this is the preferred way to iterate over your items.

Example

// prep code
var items = new Array(1000); // empty array with 1000 items

// the actual loop code
var i = items.length;
while (i--) { .. stuff .. }

for()

If the loop direction doesn't allow you to use a while loop, make sure that you cache all the variables in your regular for loop to ensure that it runs in the most performant way.

Example

for (var i = 0, length = items.lenght; i < length; i++) {
  .. code ..
}

forEach()

forEach loops should be avoided as they introduce a function at each iteration. It's not forbidden to use it, but be careful when working with it in performance critical code.

whitespace

line breaks

When you are writing block scopes there should be a new line above and below the block. If there is a comment above the block, then the new line should be placed above the comment.

In addition to block such as if, switch, try catch statements, loops should also be wrapped in new lines.

If you have a bunch of variables specified should also add a line break below it.

There should never be 2 line breaks after each other.

Example

function test () {
  var x = 1;
  -- line break --
    if (x === 1) alert();
  -- line break --
    return x;
}

spaces

The parentheses of an if statement should be wrapped with on the outside with 1 single space. The inside of the parentheses should not hold and extra spaces.

Example

// good
if (pewpew) console.log('zing ping pauw pauw');

// bad
if( pewpew )console.log('zing ping pauw pauw');

Braces

Braces should always be on the same line of the ending parentheses. The closing brace should always be placed on the next line.

If a block statement fits on single line, and only contains one single value it's fine to omit the braces. In all other cases the braces are required.

The notable exception for this rule are try {} catch (e) {} statements. If the code that needs to be catched fits on one single line, it's okay to place the closing brace on the same line as the opening brace. The braces should be padded on the inside with 1 space on both sides.

Example

if (foo) return x;
while (i--) fn(i);

// required brackets
if (thisArg === 4) {
  fn(); fn2();
}

// tiny try catch
try { JSON.parse('function(){}') }
catch (e) { alert(e) }

Reasoning

This prevents common pitfalls in JavaScript if you assume that code under a statement is always correctly excited. But this is not the case for return statements. See the following example code as illustration of this issue:

return
{} // this actually returns undefined

return {} // returns an object

Functions

Put your balls inside #

When writing immediate invoking functions put your function balls inside.

Example

(function balls () {
 .. stuff..
}())

Name your anonymous functions

Always name your anonymous functions and expressions. If you do not name your functions it might show up as anonymous function your error's stack trace, making it really hard to debug and track down potential errors.

Naming your functions does increase the total file size of your development file, but a good minification process would remove these extra names from your functions.

When you write the function name make sure you slap spaces around them.

Example

[1, 2, 3].forEach(function forEach (val, index, arr) {
  console.log('iteration %d of %d', val, arr.length);
});

var x = function x () {}

Constructors

When you define a function as a constructor the function name should start with an UpperCase character.

Function declarations

Use function declarations over function expressions as you don't have to worry about naming your anonymous function and it greatly reduces the indentation level in your code.

The only exception for this case is when you need to conditionally assign a function, that first create the variable and assign the function to that variable.

See nfe

jQuery

jQuery allows you write long lines of spaghetti code because of it's chained nature. If you want to pursue this type of code style you should correctly break up long lines on where the DOM flow ends or is grouped by functionality.

If you are assigning event listeners using jQuery consider writing functions declarations and supply the event listeners with a reference to the correct function declaration.

// assume that moo, click and highlight are pre-declared
// functions

// single line
$('body').addClass('pew pew')

// grouping
$('body')
  .addClass('pewpew')
    .removeClass('pew-pong');

// follow the internal DOM structure of jQuery
$('body')
  .find('.cows')
    .addClass('moo')
    .on('click', moo)
  .end()
  .find('.milk')
    .addClass('white')
    .find(':empty')
       .remove();

$('input[data="wtf"]').addClass('red')
  .on('click', click)
  .on('mousemove', highlight);

Exception handling

Try to avoid throwing your own Error instances as the current JIT compilers are not optimized for such cases and will bail out with optimizations.

It also assumes that all your JavaScript is executed in synchronous while we are shifting to an asynchronous environment.

Instead of throwing and error return an Error instance. If you have an error in an asynchronous call adopt an error first callback pattern where your callback function always receives the first argument as an error.

When working with an error first callback pattern it's important to return early so you don't nest you code to deeply and there for avoiding callback tree's

Example

/**
 * Async worker thingy.
 *
 * @param {Array} arr
 * @param {Function} cb
 * @api private
 */

function async (arr, cb) {
  .. do work ..
  setTimeout(function timeout () {
    cb(new Error('Failed to process array'));
  }, 10)
}

async([1, 2, 3, 4, 5], function done (err, data) {
  // return early
  if (err) return console.error('Failed bla bla', err);
});
Something went wrong with that request. Please try again.