The following document is a set of rules or conventions that I take from many others style guides.
This guide does not pretend to substitute the excellent other guides that are out there, is just my personal cheat sheet that I use to work and I decided to share it.
I use ECMAScript 6 specification and Babel as a compiler.
Also, I have another project js-test-bench that could be used as a playground to test the conventions or rules that are here.
I have different JavaScript style guides as a reference. In fact, this style guide is a result of gather many tips from various places.
Here, some of this style guides that I found interesting or useful.
- 1.1 Be descriptive with your naming, avoid single letter names and too many long ones. ESLint:
id-length
// avoid
function q() {
// code..
}
// recommended
function getQueryObject() {
// code..
}
// avoid
let SOMEobj = {};
function get-query-object() {
// code...
};
// recommended
let someObj = {};
function getQueryObject() {
// code...
}
// avoid
class bankAccount {
constructor(param1){
// code...
}
}
const myAccount = new bankAccount('Something');
// recommended
class BankAccount {
constructor(param1){
// code...
}
}
const myAccount = new BankAccount('Something');
// avoid
const userPermissions = 0777;
// recommended
const USER_PERMISSIONS = 0777;
- 1.5 Do not use trailing or leading underscores. ESLint:
no-underscore-dangle
.
This rule is for the long history of using dangling underscores to indicate "private" members of objects, take more information about that in the ESLint rule no-underscore-dangle.
// avoid
this._someValue = 'Something';
this._someNumber_ = 17;
// recommended
this.someValue = 'Something';
this.someNumber = 17;
// recommended
There is no Why explanation in these statements because all are very personal preferences, in most of them are open discussions in the community, so you don't have a clear answer about it.
// avoid
function foo(){
∙∙const bar;
}
// recommended
function foo(){
∙∙∙∙const bar;
}
- 2.2 Place 1 space before blocks. ESLint:
space-before-blocks
.
// avoid
if (foo){
bar();
}
function bar(){
console.log('bar() function');
}
// recommended
if (foo) {
bar();
}
function bar() {
console.log('bar() function');
}
- 2.3 Place 1 space before the opening parenthesis in control statements (
import
,export
,class
,if
,while
, etc). Place no space in function name in function calls and declarations. ESLint:keyword-spacing
.
// avoid
if(foo) {
bar();
}
function bar () {
console.log('bar() function');
}
// recommended
if (foo) {
bar();
}
function bar() {
console.log('bar() function');
}
- 2.4 Place spaces around operators. ESLint:
space-infix-ops
.
// avoid
const sum=a+b;
// recommended
const sum = a + b;
- 2.5 Require and indented new line when making long method chains (more than two method chains) or when are too large. Use a leading dot to emphasizes that the line is not a new statement. ESLint:
newline-per-chained-call
.
// avoid
angular.module('fooModule', []).component('someComponent', SomeComponent).name;
doSomething().then(doSomethingElse).then(result => { console.log(result) });
// recommended
angular
.module('fooModule', [])
.component('someComponent', SomeComponent)
.name;
doSomething()
.then(doSomethingElse)
.then( result => {
console.log(result);
});
- 2.6 Leave a blank line after blocks and before the next statement.
// avoid
if (foo) {
bar();
}
return baz;
const arr = [
function foo() {
},
function bar() {
}
];
return arr;
// recommended
if (foo) {
bar();
}
return baz;
const arr = [
function foo() {
},
function bar() {
}
]
- 2.7 Do not add spaces inside a parenthesis. ESLint:
space-in-parens
.
// avoid
const math = ( 2 + 2 ) * 3;
foo(math, [2,4] );
// recommended
const math = (2 + 2) * 3;
foo(math, [2,4]);
- 2.8 Do not add space inside brackets. ESLint:
array-bracket-spacing
.
// avoid
const arr = [ 'foo', 'bar' ];
const numArr = [ 1, 2, 3, 4 ];
// recommended
const arr = ['foo', 'bar'];
const numArr = [1, 2, 3, 4];
- 2.9 Do not add spaces inside curly braces. ESLint:
object-curly-spacing
.
// avoid
const obj = { foo: [1,2], bar: 'baz', baz: 'bar', num: 17 };
// recommended
const obj = {foo: [1,2], bar: 'baz', baz: 'bar', num: 17};
- 2.10 Avoid having lines of code that are longer than 80 characters (including whitespaces). Except for strings. ESLint:
max-len
. ESLint:object-curly-spacing
// avoid
const obj = {bar: 'this pretends to be a long object', foo: {arr: [1,2,3,4], baz: 'this also pretends to be a long object'}};
// recommended
const obj = {
bar: 'this pretends to be a long object',
foo: {
arr: [1,2,3,4],
baz: 'this also pretends to be a long object'
}
};
Why? because ES6 modules are always in strict mode. Here the spec.
- 3.2 Use always ES6 modules (
import
/export
) instead of a non-standard module system.
// avoid (AMD)
define(function(){
return something;
});
// avoid (CommonJS)
module.exports = 'something';
// recommended
import assets from './assets';
// code...
const api = {
getTask
}
export default api;
- 3.3 Put all the imports above. ESLint plugin:
import/first
Why? Imports are hoisted, keeping them all at the top, give up consistency and better developer experience.
// avoid
import library1 from 'library1';
library1.init();
import library2 from 'library2';
// recommended
import library1 from 'library1';
import library2 from 'library2';
library1.init();
- 3.4 Do not use wildcard imports.
Why? This makes sure you have a single default export.
// avoid
import * as foo from 'foo';
// recommended
import foo from 'foo';
- 3.5 Only import from a path in one place. ESLint:
no-duplicate-imports
Why? Import from the same path in different lines can make code harder to maintain.
// avoid
import library1 from 'library1';
import { function1, function2 } from 'library1';
// recommended
import library1 , { function1, function2 } from 'library1';
- 3.6 Put all the exports at the end of the module file.
Why? Becomes evident what you are exporting instead of having to crawl around the module.
// avoid
const numA = 17;
const numB = 21;
export default numA;
const numC = 44;
// recommended
const numA = 17;
const numB = 21;
const numC = 44;
export default numA;
- 3.7 Always use a default export, and export an API object. ESLint plugin:
import/prefer-default-export
Why? Pony Foo.
Why? Consistency, having one clear way to export make things consistent.
// avoid
let sum = (a,b) => a+b:
const foo = 17;
export {sum, foo};
// recommended
let sum = (a,b) => a+b;
const foo = 17;
const api = {
sum,
foo
}
export default api;
- 3.8 Do not export mutable bindings. ESLint plugin:
import/no-mutable-exports
Why? Mutation should be avoided in general, but in particular when exporting mutable bindings.
Note: If you use the technique described above, "Always use a default export, an export and API" You just have to think about that when working with code from a third party.
// avoid
let numA = 17;
let numB = 22;
export { numA, numB };
// recommended
const numA = 17;
const numB = 22;
const api = {
numA,
numB
}
export default api;
- 3.9 Never export directly from an import.
Why? One line is shorter and concise, but having one clear way to import and export makes things consistent and better for the developer experience.
// avoid
export { FooAction, BarAction } from './actionCreators.js';
// recommended
import { FooAction, BarAction} from './actionCreators.js';
const api = {
FooAction,
BarAction
}
export default api;
- 5.1 Always use
class
. Avoid manipulatingprototype
directly.
Why? Javascript classes introduced in ECMAScript 6, are syntactical sugar over prototype-based inheritance and provide a much simpler and clear syntax to create objects and deal with class inheritance.
// avoid
function Vehicle(type, wheels){
this.type = type;
this.wheels = wheels;
}
Vehicle.prototype.toString = function(){
return "Vehicle type: "+this.type+", Wheels: "+this.wheels;
}
// recommended
class Vehicle {
constructor(type, wheels){
this.type = type;
this.wheels = wheels;
}
toString(){
return "Vehicle type: "+this.type+", Wheels: "+this.wheels;
}
}
- 4.2 Use
extends
for inheritance.
Why? It is and abstraction to inherit prototype, again, much simpler and clear than classical prototype inheritance.
Note: in inheritance, Vehicle
class is a base class, and Car
is a derived class.
// avoid
function Vehicle(type, wheels){
this.type = type;
this.wheels = wheels;
}
Vehicle.prototype.toString = function(){
return "Vehicle type: "+this.type+", Wheels: "+this.wheels;
}
Car.prototype = new Vehicle('Car',4);
Car.prototype.constructor = Car;
function Car(brand){
this.brand = brand;
}
Car.prototype.toString = function(){
return "Vehicle type: "+this.type+", Wheels: "+this.wheels+", Brand: "+this.brand;
}
// recommended
class Vehicle {
constructor(type, wheels){
this.type = type;
this.wheels = wheels;
}
toString(){
return "Vehicle type: "+this.type+", Wheels "+this.wheels;
}
}
class Car extends Vehicle {
constructor(type, wheels, brand){
super(type, wheels);
this.brand = brand;
}
toString(){
super.toString()+", Brand "+this.brand;
}
}
- 4.3 Uses super calls: superconstructor and superproperties.
class A {
constructor(prop1, prop2){
this.prop1 = prop1;
this.prop2 = prop2;
}
toString(){
return 'Prop1: '+this.prop1+', Prop2: '+this.prop2;
}
}
class B extends A {
constructor(prop1, prop2, prop3){
super(prop1, prop2);
this.prop3 = prop3;
}
toString(){
return super.toString()+', Prop3: '+this.prop3;
}
}
- 4.4 Classes have a default constructor if one is not specified. An empty constructor function or one that just delegates to a parent class is unnecessary. ESLint:
no-useless-constructor
.
// avoid
class A { // Base class
constructor(){
}
}
class A extends B { // Derived class
constructor(..args){
super(...args);
}
}
// recommended
class A { // Base class
}
class A extends B { // Derived class
constructor(...args){
super(...args);
this.foo = 'bar';
}
}
- 4.5 Avoid duplicate class members. ESLint:
no-dupe-class-members
.
Why? The last declaration overwrites other declarations silently.
class Foo {
bar() {
return true;
}
bar() {
return false;
}
}
const foo = new Foo();
foo.bar(); // false
- 5.1 Use always braces with all multi-line blocks and put the next condition in the same place that the first close the brace (the clearest example of that is with an
if
else
statement). ESLint:brace-style
Why? to follow the one true brace style
// avoid
function foo(){ return false; }
if(something)
return true;
if(true){
return false;
}
else{
return true;
}
// recommended
function foo(){
return false;
}
if(something){
return true;
}
if(true){
return false;
}else{
return true;
}
Why? let
and const
are block scoped instead of var
, that is a function scoped.
// avoid
var foo = "bar";
var VERSION = 1;
// recommended
let foo = "bar";
const VERSION = 1;
- 6.2 Always use
const
to declare variables that don't need to reassign. ESLint:prefer-const
.
Why? const
declaration means the variable is never reassigned, reducing cognitive load and improving maintainability.
// avoid
for(let foo in [1,2,3]){ // foo is redefined (not reassigned) on each loop step.
console.log(foo);
}
for(const foo = 0; foo < 3; foo+=1){ // ERROR. foo is reassigned with a new value each loop step.
console.log(foo);
}
// recommended
for(const foo in [1,2,3]){
console.log(foo);
}
for(let foo = 0; foo < 3; foo+=1){
console.log(foo);
}
Why? Mainly for the style but also for developer experience (always use a ;
at the end of a declaration).
// avoid
const foo,
bar;
let fox,
box;
// recommended
const foo;
const bar;
let fox;
let box;
- 6.4 Group all your
const
s and then group all yourlet
s.
Why? Improve developer experience.
// avoid
const favNum = 17;
let foo = 'bar';
const i;
let counter;
// recommended
const favNum = 17;
const i;
let foo = 'bar';
let counter;
- 6.5 Not use unary
++
increments and--
decrements. ESLint:no-plusplus
.
Why? From ESLint documentation: the unary ++
and --
operators are subject to automatic semicolon insertion, differences in whitespace can change semantics of source code.
Why? Is more clear to mutate values with num += 1
than num++
.
// avoid
let counter = 0;
counter++;
// recommended
let counter = 0;
counter +=1;