/style-guide

Entize's JavaScript Style Guide

Entize JavaScript Style Guide

Table of contents

  1. Banned features and gotchas
  2. Naming
  3. Whitespace
  4. Punctuation
  5. Comments
  6. Inline documentation
  7. Types
  8. Conditionals
  9. Loops and comprehensions
  10. Modules
  11. Resources
  12. License

Banned features and gotchas

Banned features

  • with
  • eval
  • new Array
  • new Object
  • __proto__
  • Modifying native objects or their prototypes
  • The comma operator, except with for loops
  • Bit math and clever optimizations

Gotchas

JavaScript is full of them! Avoid being clever. If you wonder whether something works or need to read a line twice, rewrite with simpler syntax.

Avoid declaring functions inside of a block, such as a conditional. This is undefined behavior, and browsers handle it differently. Instead, assign the function to a variable.

// No
if (currentUser) {
  function test() {
    console.log('Nope.');
  }
}

// Yes
if (currentUser) {
  var test = function test() {
    console.log('Yup.');
  };
}

Avoid implicit type coercion, especially with == and !=. Instead, always use === and !==. The exception is that item == null is OK.

Be careful when adding numbers to explicitly cast to a number type. Otherwise, you might accidentally perform string concatenation if one of the arguments is a string.

Use a null check of if (value == null) instead of if (!value) when value can be any number. It is easy to forget that zero is falsey.

All JavaScript numbers are doubles. Be careful when using large integers, doing decimal math, and when casting numbers to and from strings. Bit operators act like 32-bit signed integers, which can lead to unexpected results.

NaN is the only value in JavaScript that does not equal itself.

[⬆]

Naming

  • lowerCamelCase - Local variables and functions as well as public properties or functions
  • _underscoreCamelCase - Internal properties or functions
  • UpperCamelCase - Constructor functions
  • SCREAMING_SNAKE_CASE - Constants
  • dashed-lower-case - HTML ids, classes, and attribute names

When camelcasing acronyms, always treat them like words. For example: htmlSection or newHtml

Avoid reserved words as property names.

Abbreviations

Do not abbreviate variable or function names unless they are explicitly listed here. If an abbreviation is listed here, always use it instead of the full word.

Generic abbreviations:

  • err - Error arguments in callbacks and catch
  • cb - Callback
  • i - Index
  • len - Length
  • e - HTML event object
  • el - HTML element
  • fn - Variable representing a function

Abbreviations especially for use in Share and Racer:

  • op - Operation
  • doc - Data document
  • v - Version
  • db - Database

When iterators are nested or functions that take a callback are nested, use specific names for the different callbacks or indices.

Spelling

Use U.S. English spelling. For words with ambiguous spelling, use the spelling in the following list:

  • indices, not indexes
  • referrer, not referer, except in HTTP headers where it is customarily mispelled

[⬆]

Whitespace

  • Two space indentation
  • No trailing line whitespace
  • No whitespace on empty lines
  • Has extra linebreak at end of file

Indentation

Indent statements continued between lines one level (two spaces). Place operators that continue a line at the end of the line.

greeting = (isNew) ?
  'Welcome!' :
  'Welcome back!';

farewell = 'Come back to our place soon ' +
  user.firstName;

In chained method calls that don't fit on a single line, place each call on a separate line and indented by one level, with a leading ..

expressApp
  .use(express.logger())
  .use(express.favicon())
  .use(expressApp.router);

Do not vertically align items in consecutive lines.

// No
var x        = 0;
var y        = 0;
var position = 'top';

// No
var magicWords = [
                   'abracadabra'
                   'gesundheit'
                   'ventrilo'
                 ];

// No
var message = (regExp.test(text)) ?
              'You have a match!' :
              'No match';

// Yes
var x = 0;
var y = 0;
var position = 'top';

// Yes
var magicWords = [
  'abracadabra'
  'gesundheit'
  'ventrilo'
];

// Yes
var message = (regExp.test(text)) ?
  'You have a match!' :
  'No match';

[⬆]

Punctuation

Quotes

Prefer single quoted strings ('') instead of double quoted ("") strings.

Semicolons

End all statements with semicolons.

Commas

Use leading commas with a single tab indentation before the comma when declaring arrays or objects.

var names = [
    'Judy'
  , 'Walt'
  , 'Ben'
  , 'Susan'
  , 'Kim'
];

Variable declarations

Use one var for each variable declaration, and declare it where the variable is first used in the function. This makes it easier to refactor code and make sure that each variable is properly declared as local. Further, when initializing the same variable in independent sections, one should redeclare it in each section. This is especially desired for loop iterators.

// No
var keys = ['foo', 'bar']
  , values = [23, 42]
  , object = {}
  , len
  , key
  , i
    ;

for (i = values.length; i--;) {
  values[i] = values[i] * 2;
}

for (i = 0, len = values.length; i < len; i++) {
  key = keys[i];
  object[key] = values[i];
}

// Yes
var keys = ['foo', 'bar'];
var values = [23, 42];

for (var i = values.length; i--;) {
  values[i] = values[i] * 2;
}

var object = {};
for (var i = 0, len = values.length; i < len; i++) {
  var key = keys[i];
  object[key] = values[i];
}

[⬆]

Comments

Capitalize the first word of the comment, unless the first word is an identifier that begins with a lower-case letter. If a comment is short, omit the period at the end. Use backticks to quote identifiers inside of comment descriptions.

Block Comments

Block comments apply to the block of code that follows them.

Each line of a block comment starts with a // and a single space, and should be indented at the same level of the code that it describes.

Paragraphs inside of block comments are separated by a line containing //.

// This is a block comment. Note that if this were a real block
// comment, we would actually be describing the proceeding code.
//
// This is the second paragraph of the same block comment. Note
// that this paragraph was separated from the previous paragraph
// by a line containing a single comment character.
start();
stop();

Inline Comments

Place inline comments on the line immediately above the statements they are describing. Do not place inline comments on the same line unless it aids in clarity, such as documenting a multi-line regular expression.

The use of inline comments should be limited, because their existence is typically a sign of a code smell. Especially do not use inline comments when they state the obvious.

// No

// Increment x
x = x + 1;

However, inline comments can be useful in certain scenarios:

// Yes

// Compensate for border
x = x + 1;

Annotations

Use annotations when necessary to describe a specific action that must be taken against the indicated block of code.

Write the annotation on the line immediately above the code that the annotation is describing.

Follow the annotation keyword by a colon and a space, and a descriptive note.

// FIXME: The client's current state should *not* affect payload processing.
resetClientState();
processPayload();

If multiple lines are required by the description, indent subsequent lines with two spaces:

// TODO: Ensure that the value returned by this call falls within a certain
//   range, or throw an exception.
analyze();

Annotation types:

  • TODO: describe missing functionality that should be added at a later date
  • FIXME: describe broken code that must be fixed
  • HACK: describe the use of a questionable coding practice
  • OPTIMIZE: describe code that is inefficient and may become a bottleneck
  • REVIEW: describe code that should be reviewed to confirm implementation
  • REFACTOR: describe code that should be refactored

[⬆]

Inline documentation

Use JSDoc to document functions and types inline. All public interfaces should be annotated.

Installing DocBlockr is recommended for Sublime Text.

/**
 * A tasty treat with a hole
 * @param {string} flavor Name of the flavor
 * @constructor
 */
function Donut(flavor) {
  this.flavor = flavor;
}

/**
 * Decorate a donut
 * @param {Donut} donut The donut to decorate
 * @param {Object} [options] Options for decorating the donut
 * @param {boolean} [options.needsSprinkles] Whether the donut needs sprinkles
 * @param {boolean} [options.needsGlaze] Whether the donut needs glaze
 * @return {Donut} The decorated donut
 */
function decorateDonut(donut, options) {
  // ...
}

[⬆]

Types

V8 and modern JavaScript compilers can better optimize code when objects follow more regular type structures. In addition, Chrome's memory profiling tools provide more rapid debugging and more useful insights when objects are of named types.

Thus, when objects are created with a regular set of properties, using a constructor is preferred to object literals. This is especially important for long-lived objects and objects that act as maps, since these are more likely to be involved in memory leaks. However, using object literals is OK for boxing up multiple arguments to get immediately parsed out and irregular blobs of data.

For performance reasons, the properties of a typed object should not be changed after instantiation. New properties should not be added dynamically, and properties should not be removed with a delete statement. Instead, properties that are only sometimes existant should be initialized to null in the constructor and cleared via a set to null. Adding properties dynamically and delete should ideally only be used on objects that act as maps.

Arrays should be avoided as containers of mixed types; they should only be used to represent lists of a single type.

// No

// Constructors are preferred even for singleton objects and simple map
// objects. This gives them type names in the memory profiler
var library = {
  isbnMap: {}
};

// Prototypes are preferred to dynamically adding methods
library.add = function(book) {
  this.isbnMap[book.isbn] = book;
  // Avoid dynamically adding or removing properties of a typed object
  book.currentLibrary = this;
};

// A constructor should be used for greater efficiency, better profiling,
// and to make it more explicit what are the optional and required fields
library.add({
  isbn: '978-1470178192'
, title: 'Moby Dick'
, author: 'Herman Melville'
});

// Yes

function Book(isbn, options) {
  options || (options = {});
  this.isbn = isbn;
  this.title = options.title;
  this.author = options.author;
  // Make sure to initialize all properties that a typed object can have in
  // the constructor. Set properties to `null` if there is no default value
  this.currentLibrary = null;
}

function IsbnMap() {}

function Library() {
  this.isbnMap = new IsbnMap;
}

Library.prototype.add = function(book) {
  this.isbnMap[book.isbn] = book;
  book.currentLibrary = this;
}

var library = new Library;

// This options object literal is fine, because it is immediately parsed
// out and can be easily garbage collected
var book = new Book('978-1470178192', {
  title: 'Moby Dick'
, author: 'Herman Melville'
});
library.add(book);

[⬆]

Conditionals

Omit curly braces for if statements that fit on one line and that don't have an else. Always write if statements that have an else on multiple lines and use curly braces.

// No
if (isNew) onNew();
else onOld();

// No
if (isNew)
  onNew();
else
  onOld();

// Yes
if (isNew) {
  onNew();
} else {
  onOld();
}

// Yes
if (err) callback(err);

To avoid nesting of if statements, always return a function's value as early as possible.

// No
function isPercentage(val) {
  if (val >= 0) {
    if (val < 100) {
      return true;
    } else {
      return false;
    }
  } else {
    return false;
  }
}

// Yes
function isPercentage(val) {
  if (val < 0) return false;
  if (val > 100) return false;
  return true;
}

[⬆]

Loops and comprehensions

Lever's suggestions on loops: Prefer for loops and not Array#forEach in cases where a closure wrapper is not needed.

I kind of like the cleanliness of using Array#forEach in many cases but appreciate the simplicity and speed of just always using for - What do you guys think?

// No
items.forEach(function(item) {
  console.log(item);
});

// Yes
for (var i = 0, len = items.length; i < len; i++) {
  var item = items[i];
  console.log(item);
}

Like returning early from a function, use continue to avoid nesting of conditionals in loops.

// No
var results = [];
for (var i = 0, len = values.length; i < len; i++) {
  var value = values[i];
  if (value >= threshold) {
    results.push(value);
  }
}

// Yes
var results = [];
for (var i = 0, len = values.length; i < len; i++) {
  var value = values[i];
  if (value < threshold) continue;
  results.push(value);
}

Use the following canonical forms of loops:

// Loop forwards
for (var i = 0, len = items.length; i < len; i++) {
  var item = items[i];
  // ...
}

// Loop backwards
for (var i = items.length; i--;) {
  var item = items[i];
  // ...
}

// Loop until undefined
while (node.firstChild) {
  node.removeChild(node.firstChild);
}

// Loop over Node.childNodes (about 3x faster than iterating through childNodes by index)
for (var node = parent.firstChild; node; node = node.nextSibling) {
  // ...
}

// Loop over Element.children (about 3x faster than iterating through children by index)
for (var el = parent.firstElementChild; el; el = el.nextElementSibling) {
  // ...
}

[⬆]

Modules

Use the Node.js module convention for both server and client code. Each module is a file. Modules may export an object with stateless methods, a single function, or a constructor.

Files

All files should be written in the following order:

  1. Require statements
  2. Constant definitions
  3. Exports declarations
  4. Implementation

Requires

Place require statements at the top of a file. Group them in the following order:

  1. Standard library imports
  2. Third party library imports
  3. Local imports

When using imported functions, don't destructure them at the top of the file. This can make it harder for others to figure out the context of the function when they are reading the code later.

// No
var join = require('path').join;
...
join(__dirname, '/foo');

// Yes
var path = require('path');
...
path.join(__dirname, '/foo');

Modules that export an object

Modules that export an object have a camel case name starting with a lowercase letter. Where either singular or plural would make sense, pluaral names are preferred.

They only export stateless functions and global constants shared among all instances of the module. Public exported functions start with a lowercase letter, and functions exported for internal use start with an underscore.

Modules that export an object only use exports and they do NOT use module.exports. All exported functions are exported where they are defined. Use of exported functions in the same file also refer to the function as exports.xxx.

fruits.js

var fs = require('fs');
var util = require('util');

var TIMEOUT = 1000;

module.exports = {
    TIMEOUT: TIMEOUT
  , ripen: ripen
  , ripenEach: ripenEach
};

function ripen(fruit, amount) {
  fruit.ripeness *= amount;
}

function ripenEach(fruits, amount) {
  for (var i = 0, len = fruits.length; i < len; i++) {
    ripen(fruit, amount);
  }
}

Modules that export a function or constructor

Modules that export a function or constructor are named the same thing as the function they export. Modules exporting a constructor start with an uppercase character.

They always export using module.exports = <name>.

Fruit.coffee

var fs = require('fs');
var util = require('util');

var TIMEOUT = 1000;

module.exports = Fruit;
Fruit.TIMEOUT = TIMEOUT;

function Fruit(name) {
  this.name = name;
  this.ripeness = 0;
}

Fruit.prototype.isRipe = function() {
  return this.ripeness > 0.5;
};

[⬆]

Resources

Read this

Other style guides

Performance

Books

Blogs

[⬆]

License

(The MIT License)

Copyright (c) 2013 Lever

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

This document forked from the Airbnb JavaScript Style Guide.

(The MIT License)

Copyright (c) 2012 Airbnb

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

[⬆]