Skip to content

calcaide/javascript-style-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

21 Commits
 
 
 
 
 
 

Repository files navigation

JavaScript style guide

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.

References

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.

Table of contents

  1. Naming conventions.
  2. Whitespace.
  3. Modules.
  4. Classes.
  5. Blocks.
  6. Variables.

Naming conventions

  • 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..
}

  • 1.2 Use lowerCamelCase when naming objects, functions, and instances. ESLint: camelCase.
// avoid
let SOMEobj = {};

function get-query-object() {
    // code...
};

// recommended
let someObj = {};

function getQueryObject() {
    // code...
}

  • 1.3 Use UpperCamelCase only when naming constructors or classes. ESLint: new-cap.
// 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;

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

⬆ back to top

Whitespace

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.

  • 2.1 Use 4 spaces for indent. ESLint: indent.
// avoid
function foo(){
∙∙const bar;
}

// recommended
function foo(){
∙∙∙∙const bar;
}

// 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');
}

// 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() {
    }
]

// avoid
const math = ( 2 + 2 ) * 3;
foo(math, [2,4] );

// recommended
const math = (2 + 2) * 3;
foo(math, [2,4]);

// avoid
const arr = [ 'foo', 'bar' ];
const numArr = [ 1, 2, 3, 4 ];

// recommended
const arr = ['foo', 'bar'];
const numArr = [1, 2, 3, 4];

// 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'
    }
};

⬆ back to top

Modules

  • 3.1 Do not write more "use strict". ESLint: strict.

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;

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';

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; 

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;

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;

⬆ back to top

Classes

  • 5.1 Always use class. Avoid manipulating prototype 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';
    }
}

Why? The last declaration overwrites other declarations silently.

class Foo {
    bar() {
        return true;
    }

    bar() {
        return false;
    }
}

const foo = new Foo();
foo.bar(); // false

⬆ back to top

Blocks

  • 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;
}

⬆ back to top

Variables

  • 6.1 Never use var, always use const or let. ESLint: no-var.

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);
}

  • 6.3 Use one const or let declaration per variable. ESLint: one-var.

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 consts and then group all your lets.

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;

⬆ back to top

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published