The Javascript style guide used at Procurios is inspired by and laid on top of the Airbnb style guide. We ended up creating our own (forking) because:
- We aim for synergy between our PHP and JS standards.
- Many existing style guides are too restrictive for our taste.
Our style guides makes a distinction between rules
and recommendation
. A rule must be followed, but it is allowed to break with recommendations if there's a good reason to do so.
- Rules
- Recommendations
Use single quotes (''
) for strings. Double quotes are allowed if the string contains a quote that would have to be escaped.
// bad
var name = "Bob Parr";
// good
var name = 'Bob Parr';
// bad
var fullName = "Bob " + this.lastName;
// good
var fullName = 'Bob ' + this.lastName;
// bad
var greeting = "Hi Bob! How is the weather today?";
// good
var greeting = "Hi Bob! How's the weather today?";
We use one of the following ways to declare a function:
// 1. A function defined with the Function constructor assigned to the variable multiply
function multiply (x, y) {
return x * y;
}
// 2. A function expression of a function named func_name assigned to the variable multiply
var multiply = function funcName (x, y) {
return x * y;
};
Both do approximately the same thing, with a few subtle differences. Instead of forcing a way to declare a function, inform yourself so you know what happens when you pick a one (read more).
There's an exception for immediately-invoked function expressions (IIFE):
;(function () {
console.log('Welcome to the Internet. Please follow me.');
})();
An IIFE starts with a semicolon to prevent errors when concatenating files.
Naming functions:
- makes code easier to read.
- gets you stacktraces that reference actual function names.
For example:
// bad
function (x, y) {
return x * y;
}
// bad
var multiply = function (x, y) {
return x * y;
}
// good
function multiply (x, y) {
return x * y;
}
// good
var multiply = function multiply (x, y) {
return x * y;
}
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
if (currentUser) {
test = function test () {
console.log('Yup.');
};
}
Always use var to declare variables. Not doing so will result in global variables. We want to avoid polluting the global namespace.
// bad
car = new Car();
// good
var car = new Car();
Use one var declaration per variable. It's easier to add new variable declarations this way, and you never have to worry about swapping out a ; for a , or introducing punctuation-only diffs.
// bad
var items = getItems(),
goSportsTeam = true,
dragonball = 'z';
// bad (compare to above, and try to spot the mistake)
var items = getItems(),
goSportsTeam = true;
dragonball = 'z';
// good
var items = getItems();
var goSportsTeam = true;
var dragonball = 'z';
// bad
if (a == b) {
// do stuff
}
// good
if (a === b) {
// do stuff
}
For more information see Truth Equality and JavaScript by Angus Croll.
// bad
if (test)
// do something
// bad
if (test) // do something
// good
if (test) {
// do something
}
// bad
function () { // do something }
// good
function () {
// do something
}
// bad
if (test) {
thing1();
thing2();
}
else {
thing3();
}
// good
if (test) {
thing1();
thing2();
} else {
thing3();
}
Include a description, specify types and values for all parameters and return values using JSDoc.
// bad
// make() returns a new element
// based on the passed in tag name
//
// @param {String} tag
// @return {Element} element
function make (tag) {
// ...stuff...
return element;
}
// good
/**
* make() returns a new element
* based on the passed in tag name
*
* @param {String} tag
* @return {Element} element
*/
function make (tag) {
// ...stuff...
return element;
}
// 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;
}
// bad
function () {
∙∙∙∙var name;
}
// bad
function () {
∙var name;
}
// good
function () {
var name;
}
// bad
function test (){
console.log('test');
}
// good
function test () {
console.log('test');
}
// bad
if(isJedi) {
fight ();
}
// good
if (isJedi) {
fight();
}
// bad
function fight() {
console.log ('Swooosh!');
}
// good
function fight () {
console.log('Swooosh!');
}
// bad
var x=y+5;
// good
var x = y + 5;
// bad
var story = [
once
,upon
,aTime
];
// good
var story = [
once,
upon,
aTime
];
// bad
var hero = {
firstName: 'Bob'
,lastName: 'Parr'
,heroName: 'Mr. Incredible'
,superPower: 'strength'
};
// good
var hero = {
firstName: 'Bob',
lastName: 'Parr',
heroName: 'Mr. Incredible',
superPower: 'strength'
};
// bad
var hero = {
firstName: 'Kevin',
lastName: 'Flynn',
};
var heroes = [
'Batman',
'Superman',
];
// good
var hero = {
firstName: 'Kevin',
lastName: 'Flynn'
};
var heroes = [
'Batman',
'Superman'
];
This can cause problems with IE6/7 and IE9 if it's in quirksmode. Also, in some implementations of ES3 would add length to an array if it had an additional trailing comma. This was clarified in ES5 (source):
Edition 5 clarifies the fact that a trailing comma at the end of an ArrayInitialiser does not add to the length of the array. This is not a semantic change from Edition 3 but some implementations may have previously misinterpreted this.
// bad
(function() {
var name = 'Skywalker'
return name
})()
// good
(function() {
var name = 'Skywalker';
return name;
})();
// good (guards against the function becoming an argument when two files with IIFEs are concatenated)
;(function() {
var name = 'Skywalker';
return name;
})();
Read more about guarding IIFEs.
// bad
var OBJEcttsssss = {};
var this_is_my_object = {};
var o = {};
function c () {}
// good
var thisIsMyObject = {};
function thisIsMyFunction () {}
// bad
function user (options) {
this.name = options.name;
}
var user = new user({
name: 'nope'
});
// good
function User (options) {
this.name = options.name;
}
var user = new User({
name: 'yup'
});
// bad
function OrderLine (options) {
this.amount = options.amount;
}
var OrderLine = new OrderLine({
amount: 100
});
// good
function OrderLine (options) {
this.amount = options.amount;
}
var orderLine = new OrderLine({
amount: 100
});
// bad
function () {
var self = this;
}
// bad
function () {
var that = this;
}
// good
function () {
var _this = this;
}
- The file should be named with PascalCase, and match the name of the single export.
- Always declare
'use strict';
at the top of the module. - Wrap your module in a IIFE if it contains dynamic dependencies.
Example:
;(function () {
'use strict';
var dependencies = [];
if (!('classList' in document.createElement('p'))) {
dependencies.push('path/to/classList.min.js');
}
define(dependencies,
/**
* @returns {ExampleModule}
*/
function () {
var ExampleModule = function () {
// stuff
}
return ExampleModule;
}
);
})();
Use the literal syntax for object creation.
// bad
var item = new Object();
// good
var item = {};
{}
has the same return value as new Object()
, but has the following advantages:
new Object()
can be shadowed (and might have a different return value than expected){}
is shorter (less typing, KISS){}
can be partially optimized at parse time
Use the literal syntax for array creation.
// bad
var items = new Array();
// good
var items = [];
Using []
has the following advantages:
- It's much faster.
- It can't be shadowed (always has the same return value).
- Less typing.
When you need to copy an array use Array#slice
. It's fast.
var len = items.length;
var itemsCopy = [];
var i;
// bad
for (i = 0; i < len; i++) {
itemsCopy[i] = items[i];
}
// good
itemsCopy = items.slice();
To convert an array-like object to an array, use Array#slice
.
function trigger () {
var args = Array.prototype.slice.call(arguments);
...
}
This will take precedence over the arguments
object that is given to every function scope.
// bad
function nope (name, options, arguments) {
// ...stuff...
}
// good
function yup (name, options, args) {
// ...stuff...
}
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;
var items = getItems();
var dragonball;
var goSportsTeam = true;
var len;
// good
var items = getItems();
var goSportsTeam = true;
var dragonball;
var length;
var i;
This helps avoid issues with variable declaration and assignment hoisting related issues.
// bad
function () {
test();
console.log('doing stuff..');
//..other stuff..
var name = getName();
if (name === 'test') {
return false;
}
return name;
}
// good
function () {
var name = getName();
test();
console.log('doing stuff..');
//..other stuff..
if (name === 'test') {
return false;
}
return name;
}
// bad - unnecessary function call
function () {
var name = getName();
if (!arguments.length) {
return false;
}
this.setFirstName(name);
return true;
}
// good
function() {
var name;
if (!arguments.length) {
return false;
}
name = getName();
this.setFirstName(name);
return true;
}
// bad
if (name !== '') {
// ...stuff...
}
// good
if (name) {
// ...stuff...
}
// bad
if (collection.length > 0) {
// ...stuff...
}
// good
if (collection.length) {
// ...stuff...
}
Use a leading dot, which emphasizes that the line is a method call, not a new statement.
// bad
var DraftOrderLine = (OrderLineApi.getOrderLine()).setPrice(1.00).setAmount(1200).setProductId(1).setDescription('Foobar');
// good
var DraftOrderLine = (OrderLineApi.getOrderLine())
.setPrice(1.00)
.setAmount(1200)
.setProductId(1)
.setDescription('Foobar');
// bad
var totalScore = this.reviewScore + '';
// good
var totalScore = '' + this.reviewScore;
// bad
var totalScore = '' + this.reviewScore + ' total score';
// good
var totalScore = this.reviewScore + ' total score';
Numbers are represented as 64-bit values, but Bitshift operations always return a 32-bit integer (source). Bitshift can lead to unexpected behavior for integer values larger than 32 bits. Discussion. Largest signed 32-bit Int is 2,147,483,647:
2147483647 >> 0 //=> 2147483647
2147483648 >> 0 //=> -2147483648
2147483649 >> 0 //=> -2147483647
// bad
function q () {
// ...stuff...
}
// good
function query () {
// ..stuff..
}
This is helpful for stack traces.
// bad
var log = function (msg) {
console.log(msg);
};
// good
var log = function log (msg) {
console.log(msg);
};
... instead of overwriting the prototype with a new object. Overwriting the prototype makes inheritance impossible: by resetting the prototype you'll overwrite the base!
function Jedi () {
console.log('new jedi');
}
// bad
Jedi.prototype = {
fight: function fight () {
console.log('fighting');
},
block: function block () {
console.log('blocking');
}
};
// good
Jedi.prototype.fight = function fight () {
console.log('fighting');
};
Jedi.prototype.block = function block () {
console.log('blocking');
};
// bad
Jedi.prototype.jump = function () {
this.jumping = true;
return true;
};
Jedi.prototype.setHeight = function (height) {
this.height = height;
};
var Luke = new Jedi();
Luke.jump(); // => true
Luke.setHeight(20); // => undefined
// good
Jedi.prototype.jump = function () {
this.jumping = true;
return this;
};
Jedi.prototype.setHeight = function (height) {
this.height = height;
return this;
};
var Luke = new Jedi();
Luke.jump()
.setHeight(20);