JavaScript Style Guide
JavaScript can get messy very quickly. This style guide will help keep our code clean.
Table of Contents
This document is based on the Airbnb JavaScript Style Guide and is a living document. Changes and additions are welcome, provided good reasoning is provided.
Types
Primitives
When you access a primitive type, you work directly on its value.
string
number
boolean
null
undefined
var foo = 1,
bar = foo;
bar = 9;
console.log(foo, bar); // => 1, 9
Complex
When you access a complex type, you work on a reference to its value.
object
array
function
var foo = [1, 2],
bar = foo;
bar[0] = 9;
console.log(foo[0], bar[0]); // => 9, 9
Table of Contents
Objects
Use literal syntax for object creation.
// bad
var item = new Object();
// good
var item = {};
Use readable synonyms in place of reserved words.
// bad
var superman = {
class: 'alien'
};
// bad
var superman = {
klass: 'alien'
};
// good
var superman = {
type: 'alien'
};
Table of Contents
Arrays
Use the literal syntax for array creation.
// bad
var items = new Array();
// good
var items = [];
If you don't know array length, use Array.push().
var someStack = [];
// bad
someStack[someStack.length] = 'abracadabra';
// good
someStack.push('abracadabra');
When you need to copy an array, use Array.slice().
var len = items.length,
itemsCopy = [],
i;
// bad
for (i = 0; i < len; i+=1) {
itemsCopy[i] = items[i];
}
// good
itemsCopy = items.slice();
To convert an array-like object to an array, use Array.slice().
var args = Array.prototype.slice.call(arguments);
Table of Contents
Strings
Use single quotes ' '
for strings.
// bad
var name = "Zach Forrest";
// good
var name = 'Zach Forrest';
// bad
var fullName = "Zach " + this.lastName;
// good
var fullName = 'Zach ' + this.lastName;
Table of Contents
Functions
Function expressions:
// anonymous function expression
var anonymous = function() {
return true;
};
// named function expression
var named = function named() {
return true;
};
// immediately-invoked function expression (IIFE)
(function() {
console.log('Welcome to the Internet. Please follow me.');
})();
Never declare a function in a non-function block (if, while, etc). Assign the function to a variable instead. Browsers will allow you to do it, but they all interpret it differently, which is bad news bears.
// bad
if (currentUser) {
function test() {
console.log('Nope.');
}
}
// good
var test;
if (currentUser) {
test = function test() {
console.log('Yup.');
};
}
Never name a parameter arguments
, this will take precedence over the
arguments
object that is given to every function scope.
// bad
function nope(name, options, arguments) {}
// good
function yup(name, options, args) {}
Table of Contents
Properties
Use dot notation when accessing properties.
var luke = {
jedi: true,
age: 28
};
// bad
var isJedi = luke['jedi'];
// good
var isJedi = luke.jedi;
Use subscript notation [ ]
when accessing properties with a variable.
var luke = {
jedi: true,
age: 28
};
function getProp(prop) {
return luke[prop];
}
var isJedi = getProp('jedi');
Table of Contents
Variables
Always use var
to declare variables. Not doing so will result in global variables. We want to avoid polluting the global namespace. Captain Planet warned us of that.
// bad
superPower = new SuperPower();
// good
var superPower = new SuperPower();
Use one var
declaration for multiple variables and declare each variable on a new line.
// bad
var items = getItems();
var goSportsTeam = true;
var dragonball = 'z';
// good
var items = getItems(),
goSportsTeam = true,
dragonball = 'z';
Declare unassigned variables last. This is helpful when later on you might need to assign a variable depending on one of the previous assigned variables.
// bad
var i, len, dragonball,
items = getItems(),
goSportsTeam = true;
// bad
var i, items = getItems(),
dragonball,
goSportsTeam = true,
len;
// good
var items = getItems(),
goSportsTeam = true,
dragonball,
length,
i;
Assign variables at the top of their scope. This helps avoid issues with variable declaration and assignment hoisting related issues.
// bad
function() {
if (!arguments.length) {
return false;
}
var name = getName();
return true;
}
// good
function() {
var name = getName();
if (!arguments.length) {
return false;
}
return true;
}
Table of Contents
Hoisting
Variable declarations get hoisted to the top of their scope, their assignment does not.
// we know this wouldn't work (assuming there
// is no notDefined global variable)
function example() {
console.log(notDefined); // => throws a ReferenceError
}
// creating a variable declaration after you
// reference the variable will work due to
// variable hoisting. Note: the assignment
// value of `true` is not hoisted.
function example() {
console.log(declaredButNotAssigned); // => undefined
var declaredButNotAssigned = true;
}
// The interpreter is hoisting the variable
// declaration to the top of the scope.
// Which means our example could be rewritten as:
function example() {
var declaredButNotAssigned;
console.log(declaredButNotAssigned); // => undefined
declaredButNotAssigned = true;
}
Anonymous function expressions hoist their variable name, but not the function assignment.
function example() {
console.log(anonymous); // => undefined
anonymous(); // => TypeError anonymous is not a function
var anonymous = function() {
console.log('anonymous function expression');
};
}
Named function expressions hoist the variable name, not the function name or the function body.
function example() {
console.log(named); // => undefined
named(); // => TypeError named is not a function
superPower(); // => ReferenceError superPower is not defined
var named = function superPower() {
console.log('Flying');
};
}
// the same is true when the function name
// is the same as the variable name.
function example() {
console.log(named); // => undefined
named(); // => TypeError named is not a function
var named = function named() {
console.log('named');
}
}
Function declarations hoist their name and the function body.
function example() {
superPower(); // => Flying
function superPower() {
console.log('Flying');
}
}
Table of Contents
Conditional Expressions & Equality
Use ===
and !==
over ==
and !=
.
Conditional expressions are evaluated using coercion with the ToBoolean
method and always follow these simple rules:
- Objects evaluate to true
- Undefined evaluates to false
- Null evaluates to false
- Booleans evaluate to the value of the boolean
- Numbers evalute to false if +0, -0, or NaN, otherwise true
- Strings evaluate to false if an empty string
' '
, otherwise true
if ([0]) {
// true
// An array is an object, objects evaluate to true
}
Use shortcuts.
// bad
if (name !== '') {
// ...stuff...
}
// good
if (name) {
// ...stuff...
}
// bad
if (collection.length > 0) {
// ...stuff...
}
// good
if (collection.length) {
// ...stuff...
}
Table of Contents
Blocks
Use braces with all multi-line blocks.
// bad
if (test)
return false;
// good
if (test) return false;
// good
if (test) {
return false;
}
// bad
function() { return false; }
// good
function() {
return false;
}
Table of Contents
Comments
Use //
for comments. Place single line comments on a newline above the subject of the comment. Put an empty line before the comment.
// bad
var active = true; // is current tab
// good
// is current tab
var active = true;
// bad
function getType() {
console.log('fetching type...');
// set the default type to 'no type'
var type = this._type || 'no type';
return type;
}
// good
function getType() {
console.log('fetching type...');
// set the default type to 'no type'
var type = this._type || 'no type';
return type;
}
Prefixing your comments with FIXME
or TODO
helps other developers quickly understand if you're pointing out a problem that needs to be revisited, or if you're suggesting a solution to the problem that needs to be implemented. These are different than regular comments because they are actionable. The actions are FIXME -- need to figure
this out
or TODO -- need to implement
.
Use // FIXME:
to annotate problems.
function Calculator() {
// FIXME: shouldn't use a global here
total = 0;
return this;
}
Use // TODO:
to annotate solutions to problems.
function Calculator() {
// TODO: total should be configurable by an options param
this.total = 0;
return this;
}
Table of Contents
Whitespace
Use soft tabs set to 2 spaces.
// good
function() {
∙∙var name;
}
// bad
function() {
∙var name;
}
// bad
function() {
∙∙∙∙var name;
}
Place 1 space before the leading brace.
// bad
function test(){
console.log('test');
}
// good
function test() {
console.log('test');
}
// bad
dog.set('attr',{
age: '1 year',
breed: 'Bernese Mountain Dog'
});
// good
dog.set('attr', {
age: '1 year',
breed: 'Bernese Mountain Dog'
});
Use indentation when making long method chains.
// bad
$('#items').find('.selected').highlight().end().find('.open').updateCount();
// good
$('#items')
.find('.selected')
.highlight()
.end()
.find('.open')
.updateCount();
Table of Contents
Commas
Leading commas: Nope.
// bad
var once
, upon
, aTime;
// good
var once,
upon,
aTime;
Additional trailing comma: Nope.
// bad
var hero = {
firstName: 'Kevin',
lastName: 'Flynn',
};
var heroes = [
'Batman',
'Superman',
];
// good
var hero = {
firstName: 'Kevin',
lastName: 'Flynn'
};
var heroes = [
'Batman',
'Superman'
];
Table of Contents
Semicolons
Yup.
// bad
(function() {
var name = 'Skywalker'
return name
})()
// good
(function() {
var name = 'Skywalker';
return name;
})();
// good
;(function() {
var name = 'Skywalker';
return name;
})();
Table of Contents
Type Casting & Coercion
Perform type coercion at the beginning of the statement.
// => this.reviewScore = 9;
// bad
var totalScore = this.reviewScore + '';
// good
var totalScore = '' + this.reviewScore;
// bad
var totalScore = '' + this.reviewScore + ' total score';
// good
var totalScore = this.reviewScore + ' total score';
Use parseInt
for Numbers and always with a radix for type casting.
var inputValue = '4';
// bad
var val = new Number(inputValue);
// bad
var val = +inputValue;
// bad
var val = inputValue >> 0;
// bad
var val = parseInt(inputValue);
// good
var val = Number(inputValue);
// good
var val = parseInt(inputValue, 10);
Booleans
var age = 0;
// bad
var hasAge = new Boolean(age);
// good
var hasAge = Boolean(age);
// good
var hasAge = !!age;
Table of Contents
Naming Conventions
Avoid single letter names. Be descriptive with your naming.
// bad
function q() {
// ...stuff...
}
// good
function query() {
// ..stuff..
}
Use camelCase when naming objects, functions, and instances.
// bad
var OBJEcttsssss = {};
var this_is_my_object = {};
function c() {};
var u = new user({
name: 'Zachary Forrest'
});
// good
var thisIsMyObject = {};
function thisIsMyFunction() {};
var user = new User({
name: 'Zachary Forrest'
});
Use PascalCase when naming constructors or classes.
// bad
function user(options) {
this.name = options.name;
}
var bad = new user({
name: 'nope'
});
// good
function User(options) {
this.name = options.name;
}
var good = new User({
name: 'yup'
});
When saving a reference to this
use self
.
function() {
var self = this;
return function() {
console.log(self);
};
}
Name your functions. This is helpful for stack traces.
// bad
var log = function(msg) {
console.log(msg);
};
// good
var log = function log(msg) {
console.log(msg);
};
Table of Contents
jQuery
Prefix jQuery object variables with a $
.
// bad
var sidebar = $('.sidebar');
// good
var $sidebar = $('.sidebar');
Cache jQuery lookups.
// bad
function setSidebar() {
$('.sidebar').hide();
// ...stuff...
$('.sidebar').css({
'background-color': 'pink'
});
}
// good
function setSidebar() {
var $sidebar = $('.sidebar');
$sidebar.hide();
// ...stuff...
$sidebar.css({
'background-color': 'pink'
});
}
For DOM queries use Cascading $('.sidebar ul')
or parent > child
$('.sidebar > ul')
.
Use find
with scoped jQuery object queries.
// bad
$('ul', '.sidebar').hide();
// bad
$('.sidebar').find('ul').hide();
// good
$('.sidebar ul').hide();
// good
$('.sidebar > ul').hide();
// good
$sidebar.find('ul');
Table of Contents
Unit Testing
Unit testing your JavaScript is strongly encouraged. Suit has the Karma testing framework built right in, so your tests can be written with Jasmine. When contributing any JavaScript to Suit, unit tests are required.
Table of Contents
jQuery Plugin Template
When writing a jQuery plugin, using the following template will make your life easier.
// [myPlugin] Plugin
// Plugin description.
// Author: null
// Last Updated: mm/dd/yyyy
// Unit Tests: [true|false]
// Do not actually name your plugin myPlugin
// jslint browser: true, eqeq: true, es5: false, regexp: true, sloppy: true, unparam: true, white: true, indent: 4, maxerr: 100
// global jQuery: true, Modernizr: true
(function($) {
'use strict';
$.tmpl = $.tmpl || function(a,b){return(String(a)).replace(/\{\{(= )?([^\{\}]+)\}\}/g,
function(c,d,e){return (b||{}).hasOwnProperty(e)?(/^f/.test(typeof b[e])?b[e]():b[e]):c;});};
$.fn.myPlugin = function(method) {
var plugin = { name: 'myPlugin', version: '1.0.0' },
methods, helpers;
helpers = {
privateFoo: function() {
// I am a function, please add some code so I can be useful
}
};
methods = {
init: function(options) {
var $opts = $.extend({}, $.fn.myPlugin.defaults, options);
return this.each(function() {
var $self = $(this).data(plugin.name, $opts);
// I am a function, please add some code so I can be useful
});
},
publicFoo: function() {
return this.each(function() {
var $self = $(this),
$opts = $self.data(plugin.name);
if ($opts) {
// the plugin has been initialized for this element, do something
}
});
}
};
// No need to change anything below here
if (typeof methods[method] === 'function') {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
}
if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
}
return $.error($.tmpl('Method {{= method}} does not exist in plugin {{= name}} {{= version}}', $.extend({method: method}, plugin)).text());
};
$.fn.myPlugin.defaults = {
foo: 'bar'
};
}(jQuery));
Table of Contents