Code style conventions
We suggest that you use the latest ES6 conventions, many are described below.
Name things logically... See reference.
Do not use
eval
. See reference.Do not use
with
. See reference.As a general rule do not use primitive type wrapper constructors:
new Number
,new String
,new Boolean
,new Array
,new Object
If the usage of the type wrapper signifigantly improves the readability of the code, then it may be considered as being worth the trade off.
- Example,
Boolean(foo)
over!!isFoo
.
- Example,
Primitive type wrappers are slower than their primitive counterparts.
They also cause subtle bugs with
instanceof
.Object's should be created using the object literal syntax.
// Bad.var obj = new Object();const obj = Object.create(Object.prototype);// Good.const obj = {};Object declarations should:
Have a space between the curly braces.
For single-line declarations, the last value should not end in a comma. For multi-line declarations, each line should end with a comma (including the last line). This will make it easier to add properties and result in a smaller diff for the reviewer.
There should not be a space before the colon, but there should be one after it.
// Bad.const a = {k1 : v1,k2: v2};const b = { k1: v1, k2 : v2, };const c = {k1:v1};// Good.const a = {k1: v1,k2: v2,};const b = { k1: v1, k2: v2 };const c = { k1: v2 };
Arrays should be created using the array literal syntax.
// Bad.const a = new Array();// Good.const a = [];
Favor
const
andlet
overvar
.var
is a legacy keyword and there are no practical use cases forvar
in modern ES6.- Never use
var
.
- Never use
const
is the default. This prevents the variable from being re-assigned, however aconst
object can still be mutated; if you need true immutability, consider usingimmutable-js
.let
only when the value of the variable must be re-assigned. This is allowed, but it is commonly a sign that you should refactor the code to prevent the need to uselet
.
Use the
class
keyword instead of thefunction
keyword for ES6 classes. IE, you should not be callingnew
against afunction
.// Bad.function MyClass() {this.someProp = "A value";}const instance = new MyClass();// Good.class MyClass {constructor(prop) {this.someProp = prop}}const instance = new MyClass();When anonymous funcitons are used, you should use a lambda / arrow functions.
const array1 = [1, 4, 9, 16];// Bad.const badMap = array1.map(function( x ) { return x * 2});const lessBadMap = array1.map(function mapData( x ) { return x * 2});// Good.const goodMap = array1.map(x => x * 2);const bestMap = array1.map((x) => x * 2);Favor arrow functions over context binding.
function myMethod(...params) { /* logic here*/ }// Bad.const self = this;const boundMethod = function(...params) {return myMethod.apply(self, params);}// Less bad.const boundMethod = myMethod.bind(this);// Best.const boundMethod = (...params) => myMethod.apply(this, params);Use destructuring when applicable.
function myFunction(person) {const { firstName, lastName } = person}Named params are preferred, however if varaidic expansion is required... always favor the ES6
...rest
syntax over inspecting thearguments
object.Options params should be passed in via an object... instead of as a named param.
This allows the options to be passed with differing data without changing how the function is called.
Additionally it helps to prevent truthiness bugs when booleans are passed. See boolean trap anti-pattern.
// Bad.function apiCall(a, b, isMock = false ) { }// Good.function apiCall(a, b, { isMock = false } = {}) {}
Dot notation usage should be consistent.
// Bad.obj.x = 123;obj['y'] = 321;// Ok, if needed.obj['x'] = 123;obj['y-z'] = 321;// Best.obj.x = 123;obj.y = 321;Always use strict comparison operators.
- Do not use
!=
and==
. - Use these instead
!==
and===
. - Prefer explicit strict value checks over truthiness checks.
- Do not use forced boolean casting =>
if(!!value)
.
- Do not use
Method chains must start with a leading dot.
// Bad.myPromise.then(() => {}).then(() => {}).catch((ex) => {})// Good.myPromise.then(() => {}).catch((ex) => {})Though the example above uses a
Promise
to demonstrate preferred syntax for method chaining, you should prefer theasync
/await
pattern over thePromise
pattern, see reference. Suggested further reading here.import axios from "axios"// Best: no await needed, just return the Promise from the async call// made within your function.async function makeGetCall(endpoint) {return axios.get(endpoint)}// Good: Await the response and return its resolved value.async function makeGetCall(endpoint) {try {const response = await axios.get(endpoint)// Do work with response.return response} catch(error) {// Handle error.}}// ########################################################################// Bad: There is no asyncronous processing happening here, so this// should not be marked as an async function.async function parseJson( jsonStr ) {try {return Promise.resolve(JSON.parse(jsonStr))}catch(error) {return Pomise.reject(error)}}// Bad: Prefer to await Promises instead of then() / catch() handlers.async function makeGetCall(endpoint) {return new Promise( (resolve, reject) => {axios.get(endpoint).then( (response) => {resolve(response)}).catch( (error) => {reject(error)})})}// Bad: When awaiting a Promise there is no need to// chain then() / catch() handlers.async function makeGetCall(endpoint) {const response = await axios.get(endpoint)// These handlers add no functional value..then( (response) => response ).catch( (error) => { throw error } );return response}// Bad: There is no need to create our own Promise, as the axios.get()// function returns a Promise that can be used as the return value.async function makeGetCall(endpoint) {return new Promise( (resolve, reject) => {try {const response = await axios.get(endpoint)resolve(response)} catch(error) {reject(error)}})}
Prefer the optional chaining (
.?
) operator over explicitnull
/undefined
/ truthy checks for nested data (requires >= node 14).let person = { employer: { name: 'Acme' }, fistName: 'John', lastName: 'Doe' };// console.log(person.employer.name) => 'Acme'// Re-defined the person, without an employer name.person = { employer: {}, fistName: 'John', lastName: 'Doe' };// Badconst coNameBad = (person.employer !== undefined &&person.employer !== null &&person.employer.name !== undefined &&person.employer.name !== null) ? person.name : undefined;// coNameBad => undefined// Good.const coNameGood = person.employer?.name;// coNameGood => undefinedPrefer the nullish coalescing operator (
??
) over explicitnull
/undefined
/ truthy checks (requires >= node 14).const y = null;// Badconst x = (y === undefined || y === null || !y) ? "default value" : y;// Good.const x = y ?? "default value"Do not nest ternary operators
(?:)
. This is a readability issue.Favor
import { myFunc } from './path/to/module';
instead ofconst myFunc = require('./path/to/module');
, see reference.Do not use the
delete
keyword unless you have to... Instead prefer setting the value of the key tonull
instead. This is a performance consideration.