JavaScript Style Guide

A mostly reasonable approach to JavaScript Edit

Table of Contents

  1. Destructuring
    1. Objects
    2. Arrays
    3. Objects over arrays
  2. Types
    1. Primitives
    2. Complex Types
  3. Commas
    1. Leading Commas
    2. Trailing Commas
  4. Naming Conventions
    1. Descriptive Naming
    2. camelCase
    3. PascalCase
    4. Leading Underscores
    5. Referencing this
    6. Exports and filenames
    7. Default exports
    8. Singletons
  5. Modules
    1. Imports over require
    2. Wildcard imports
    3. No export from import
    4. Duplicate imports
    5. Immutable exports
    6. Default exports
    7. Imports first
  6. Iterators and Generators
    1. Don’t use iterators
    2. Don’t use generators for now
    3. Generators and Spacing
  7. References
  8. Footnotes

Destructuring

Destructuring saves you from creating temporary references for those properties.

Objects

Use object destructuring when accessing and using multiple properties of an object1.

// bad
function getFullName(user) {
  const firstName = user.firstName;
  const lastName = user.lastName;

  return `${firstName} ${lastName}`;
}

// good
function getFullName(user) {
  const { firstName, lastName } = user;
  return `${firstName} ${lastName}`;
}

// best
function getFullName({ firstName, lastName }) {
  return `${firstName} ${lastName}`;
}

Arrays

Use array destructuring 2.

const arr = [1, 2, 3, 4];

// bad
const first = arr[0];
const second = arr[1];

// good
const [first, second] = arr;

Objects over arrays

Use object destructuring for multiple return values, not array destructuring. This allows you to add new properties over time or change the order of things without breaking call sites 3.

// bad
function processInput(input) {
  // then a miracle occurs
  return [left, right, top, bottom];
}

// the caller needs to think about the order of return data
const [left, __, top] = processInput(input);

// good
function processInput(input) {
  // then a miracle occurs
  return { left, right, top, bottom };
}

// the caller selects only the data they need
const { left, top } = processInput(input);

Types

Primitives

When you access a primitive type you work directly on its value.

const foo = 1;
let bar = foo;

bar = 9;

console.log(foo, bar); // => 1, 9

Complex Types

When you access a complex type you work on a reference to its value; changes to the value (as long as it’s not overwritten with a new value) will mutate the pointer value.

const foo = [1, 2];
const bar = foo;

bar[0] = 9;

console.log(foo[0], bar[0]); // => 9, 9

Commas

Leading Commas

Don’t use leading commas 4, 5.

// bad
const story = [
    once
  , upon
  , aTime
];

// good
const story = [
  once,
  upon,
  aTime,
];

// bad
const hero = {
    firstName: 'Ada'
  , lastName: 'Lovelace'
  , birthYear: 1815
  , superPower: 'computers'
};

// good
const hero = {
  firstName: 'Ada',
  lastName: 'Lovelace',
  birthYear: 1815,
  superPower: 'computers',
};

Trailing Commas

Do use trailing commas. This leads to cleaner git diffs. Transpilers like Babel will remove the additional trailing comma in the transpiled code, which means you don’t have to worry about the trailing comma problem in legacy browsers 6, 7.

// bad - git diff without trailing comma
const hero = {
     firstName: 'Florence',
-    lastName: 'Nightingale'
+    lastName: 'Nightingale',
+    inventorOf: ['coxcomb chart', 'modern nursing']
};

// good - git diff with trailing comma
const hero = {
     firstName: 'Florence',
     lastName: 'Nightingale',
+    inventorOf: ['coxcomb chart', 'modern nursing'],
};
// bad
const hero = {
  firstName: 'Dana',
  lastName: 'Scully'
};

const heroes = [
  'Batman',
  'Superman'
];

// good
const hero = {
  firstName: 'Dana',
  lastName: 'Scully',
};

const heroes = [
  'Batman',
  'Superman',
];

Naming Conventions

Descriptive Naming

Be descriptive with your naming. Avoid single letter names 8.

// bad
function q() {
  // ...stuff...
}

// good
function query() {
  // ..stuff..
}

camelCase

Use camelCase when naming objects, functions, and instances 9, 10.

// bad
const OBJEcttsssss = {};
const this_is_my_object = {};
function c() {}

// good
const thisIsMyObject = {};
function thisIsMyFunction() {}

PascalCase

Use PascalCase only when naming constructors or classes 11, 12.

// bad
function user(options) {
  this.name = options.name;
}

const bad = new user({
  name: 'nope',
});

// good
class User {
  constructor(options) {
    this.name = options.name;
  }
}

const good = new User({
  name: 'yup',
});

Leading Underscores

Do not use trailing or leading underscores 13, 14. JavaScript does not have the concept of privacy in terms of properties or methods. Although a leading underscore is a common convention to mean “private”, these properties are in fact fully public, and as such, are part of your public API contract. This convention might lead developers to wrongly think that a change won’t count as breaking, or that tests aren’t needed. If you want something to be “private”, it must not be observably present; i.e. a closure.

// bad
this.__firstName__ = 'Panda';
this.firstName_ = 'Panda';
this._firstName = 'Panda';

// good
this.firstName = 'Panda';

function closure() {
  const privateFunction = () => {
    //...
  }
}

Referencing this

Don’t save references to this. Use arrow functions or Function#bind instead 15.

// bad
function foo() {
  const self = this;
  return function () {
    console.log(self);
  };
}

// bad
function foo() {
  const that = this;
  return function () {
    console.log(that);
  };
}

// good
function foo() {
  return () => {
    console.log(this);
  };
}

Exports and filenames

A base filename should exactly match the name of its default export.

// file 1 contents
class CheckBox {
  // ...
}
export default CheckBox;

// file 2 contents
export default function fortyTwo() { return 42; }

// file 3 contents
export default function insideDirectory() {}

// in some other file
// bad
import CheckBox from './checkBox'; // PascalCase import/export, camelCase filename
import FortyTwo from './FortyTwo'; // PascalCase import/filename, camelCase export
import InsideDirectory from './InsideDirectory'; // PascalCase import/filename, camelCase export

// bad
import CheckBox from './check_box'; // PascalCase import/export, snake_case filename
import forty_two from './forty_two'; // snake_case import/filename, camelCase export
import inside_directory from './inside_directory'; // snake_case import, camelCase export
import index from './inside_directory/index'; // requiring the index file explicitly
import insideDirectory from './insideDirectory/index'; // requiring the index file explicitly

// good
import CheckBox from './CheckBox'; // PascalCase export/import/filename
import fortyTwo from './fortyTwo'; // camelCase export/import/filename
import insideDirectory from './insideDirectory'; // camelCase export/import/directory name/implicit "index"
// ^ supports both insideDirectory.js and insideDirectory/index.js

Default exports

Use camelCase when you export-default a function. Your filename should be identical to your function’s name.

function makeStyleGuide() {
}

export default makeStyleGuide;

Singletons

Use PascalCase when you export a constructor, class, singleton, function library or bare object.

const DeliverooStyleGuide = {
  es6: {
  }
};

export default DeliverooStyleGuide;

Modules

Imports over require

Always use modules (import/export) over a non-standard module system. You can always transpile to your preferred module system. Modules are the future: let’s start using the future now.

// bad
const DeliverooStyleGuide = require('./DeliverooStyleGuide');
module.exports = DeliverooStyleGuide.es6;

// ok
import DeliverooStyleGuide from './DeliverooStyleGuide';
export default DeliverooStyleGuide.es6;

// best
import { es6 } from './DeliverooStyleGuide';
export default es6;

Wildcard imports

Do not use wildcard imports. This makes sure you have a single default export.

// bad
import * as DeliverooStyleGuide from './DeliverooStyleGuide';

// good
import DeliverooStyleGuide from './DeliverooStyleGuide';

No export from import

Do not export directly from an import. Although the one-liner is concise, having one clear way to import and one clear way to export makes things consistent.

// bad
// filename es6.js
export { es6 as default } from './DeliverooStyleGuide';

// good
// filename es6.js
import { es6 } from './DeliverooStyleGuide';
export default es6;

Duplicate imports

Only import from a path in one place. Having multiple lines that import from the same path can make code harder to maintain 16.

// bad
import foo from 'foo';
// … some other imports … //
import { named1, named2 } from 'foo';

// good
import foo, { named1, named2 } from 'foo';

// good
import foo, {
  named1,
  named2,
} from 'foo';

Immutable exports

Do not export mutable bindings. Mutation should be avoided in general, but in particular when exporting mutable bindings. While this technique may be needed for some special cases, in general, only constant references should be exported 17.

// bad
let foo = 3;
export { foo }

// good
const foo = 3;
export { foo }

Default exports

In modules with a single export, prefer default export over named export 18.

// bad
export function foo() {}

// good
export default function foo() {}

Imports first

Put all import statements above non-import statements. Since import statements are hoisted, keeping them all at the top prevents surprising behavior 19.

// bad
import foo from 'foo';
foo.init();

import bar from 'bar';

// good
import foo from 'foo';
import bar from 'bar';

foo.init();

Iterators and Generators

Don’t use iterators

Prefer JavaScript’s higher-order functions instead of loops like for-in or for-of20 21. You should always strive to write many small pure functions. For loops are less contained and more difficult to reason about.

Use map() / every() / filter() / find() / findIndex() / reduce() / some() / … to iterate over arrays, and Object.keys() / Object.values() / Object.entries() to produce arrays so you can iterate over objects.

const numbers = [1, 2, 3, 4, 5];

// bad
let sum = 0;
for (let num of numbers) {
  sum += num;
}
sum === 15;

// good
const sum = numbers.reduce((total, num) => total + num, 0);
sum === 15;

Don’t use generators for now

They don’t transpile well to ES5.

Generators and Spacing

If you must use generators, or if you disregard our advice, make sure their function signature is spaced properly22. function and * are part of the same conceptual keyword - * is not a modifier for function, function* is a unique construct, different from function.

// bad
function * foo() {
}

const bar = function * () {
}

const baz = function *() {
}

const quux = function*() {
}

function*foo() {
}

function *foo() {
}

// very bad
function
*
foo() {
}

const wat = function
*
() {
}

// good
function* foo() {
}

const foo = function* () {
}

References

This guide is taken in part from the following sources:

Footnotes