Skip to content

etraveli/javascript-style-guide

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

55 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Javascript Style Guide

This is how we write Javascript code.

Other Style Guides

Table of Contents

  1. References
  2. Objects
  3. Arrays
  4. Destructuring
  5. Strings
  6. Functions
  7. Arrow Functions
  8. Modules
  9. Iterators and Generators
  10. Properties
  11. Variables
  12. Comparison Operators & Equality
  13. Blocks
  14. Comments
  15. Whitespace
  16. Naming Conventions

References

  • 1.1 Use const for all of your references; avoid using var. eslint: prefer-const, no-const-assign

    Why? This ensures that you can’t reassign your references, which can lead to bugs and difficult to comprehend code.

    // bad
    var a = 1;
    var b = {};
    let c = 2;
    let d = [];
    
    // good
    const a = 1;
    const b = {};
    const c = [];

  • 1.2 If you must reassign references, use let instead of var. eslint: no-var

    Why? let is block-scoped rather than function-scoped like var.

    // bad
    var count = 1;
    if (true) {
      count += 1;
    }
    
    // good, use the let.
    let count = 1;
    if (true) {
      count += 1;
    }

⬆ back to top

Objects

  • 2.1 Use the literal syntax for object creation. eslint: no-new-object

    // bad
    const item = new Object();
    
    // good
    const item = {};

  • 2.2 Only quote properties that are invalid identifiers. eslint: quote-props

    Why? In general we consider it subjectively easier to read. It improves syntax highlighting, and is also more easily optimized by many JS engines.

    // bad
    const bad = {
      'foo': 3,
      'bar': 4,
      'data-blah': 5
    };
    
    // good
    const good = {
      foo: 3,
      bar: 4,
      'data-blah': 5
    };

  • 2.3 Prefer the object spread operator over Object.assign to shallow-copy objects. Use the object rest operator to get a new object with certain properties omitted.

    // very bad
    const original = { a: 1, b: 2 };
    const copy = Object.assign(original, { c: 3 }); // this mutates `original` ಠ_ಠ
    delete copy.a; // so does this
    
    // bad
    const original = { a: 1, b: 2 };
    const copy = Object.assign({}, original, { c: 3 }); // copy => { a: 1, b: 2, c: 3 }
    
    // good
    const original = { a: 1, b: 2 };
    const copy = { ...original, c: 3 }; // copy => { a: 1, b: 2, c: 3 }
    
    const { a, ...noA } = copy; // noA => { b: 2, c: 3 }

⬆ back to top

Arrays

  • 3.1 Use the literal syntax for array creation. eslint: no-array-constructor

    // bad
    const items = new Array();
    
    // good
    const items = [];

  • 3.2 Use Array#push instead of direct assignment to add items to an array.

    const someStack = [];
    
    // bad
    someStack[someStack.length] = 'abracadabra';
    
    // good
    someStack.push('abracadabra');

  • 3.3 Use array spreads ... to copy arrays.

    // bad
    const len = items.length;
    const itemsCopy = [];
    let i;
    
    for (i = 0; i < len; i += 1) {
      itemsCopy[i] = items[i];
    }
    
    // good
    const itemsCopy = [...items];

  • 3.4 To convert an iterable object to an array, use spreads ... instead of Array.from.

    const foo = document.querySelectorAll('.foo');
    
    // good
    const nodes = Array.from(foo);
    
    // best
    const nodes = [...foo];

  • 3.5 Use Array.from for converting an array-like object to an array.

    const arrLike = { 0: 'foo', 1: 'bar', 2: 'baz', length: 3 };
    
    // bad
    const arr = Array.prototype.slice.call(arrLike);
    
    // good
    const arr = Array.from(arrLike);

  • 3.6 Use Array.from instead of spread ... for mapping over iterables, because it avoids creating an intermediate array.

    // bad
    const baz = [...foo].map(bar);
    
    // good
    const baz = Array.from(foo, bar);

  • 3.7 Use return statements in array method callbacks. It’s ok to omit the return if the function body consists of a single statement returning an expression without side effects, following eslint: array-callback-return

    // good
    [1, 2, 3].map(x => {
      const y = x + 1;
      return x * y;
    });
    
    // good
    [1, 2, 3].map(x => {
      return x + 1;
    });
    
    // good
    [1, 2, 3].map(x => x + 1);
    
    // bad - no returned value means `acc` becomes undefined after the first iteration
    [[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
      const flatten = acc.concat(item);
      acc[index] = flatten;
    });
    
    // good
    [[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
      const flatten = acc.concat(item);
      acc[index] = flatten;
      return flatten;
    });
    
    // bad
    inbox.filter(msg => {
      const { subject, author } = msg;
      if (subject === 'Mockingbird') {
        return author === 'Harper Lee';
      } else {
        return false;
      }
    });
    
    // good
    inbox.filter(({ subject, author }) => {
      if (subject === 'Mockingbird') {
        return author === 'Harper Lee';
      }
    
      return false;
    });
    
    // best
    inbox.filter(
      ({ subject, author }) =>
        subject === 'Mockingbird' ? author === 'Harper Lee' : false
    );

⬆ back to top

Destructuring

  • 4.1 Use object destructuring when accessing and using multiple properties of an object. eslint: prefer-destructuring

    Why? Destructuring saves you from creating temporary references for those properties.

    // bad
    const getFullName = user => {
      const firstName = user.firstName;
      const lastName = user.lastName;
    
      return `${firstName} ${lastName}`;
    };
    
    // good
    const getFullName = user => {
      const { firstName, lastName } = user;
      return `${firstName} ${lastName}`;
    };
    
    // best
    const getFullName = ({ firstName, lastName }) => `${firstName} ${lastName}`;

  • 4.2 Use array destructuring. eslint: prefer-destructuring

    const arr = [1, 2, 3, 4];
    
    // bad
    const first = arr[0];
    const second = arr[1];
    
    // good
    const [first, second] = arr;

⬆ back to top

Strings

  • 4.3 When programmatically building up strings, use template strings instead of concatenation. eslint: prefer-template template-curly-spacing

    Why? Template strings give you a readable, concise syntax with proper newlines and string interpolation features.

    // bad
    const sayHi = name => 'How are you, ' + name + '?';
    
    // bad
    const sayHi = name => ['How are you, ', name, '?'].join();
    
    // good
    const sayHi = name => `How are you, ${name}?`;

⬆ back to top

Functions

  • 4.4 Always destructure if possbile

    // bad
    const callMe = person =>
      `${person.name} wants you to call this number: ${person.number}`;
    
    // good
    const callMe = ({ name, number }) =>
      `${name} wants you to call this number: ${number}`;
    
    // good
    const callMe = person =>
      person
        ? `${person.name} wants you to call this number: ${person.number}`
        : null;

  • 4.5 Never use arguments, opt to use rest syntax ... instead. eslint: prefer-rest-params

    Why? ... is explicit about which arguments you want pulled. Plus, rest arguments are a real Array, and not merely Array-like like arguments.

    // bad
    const concatenateAll = () => {
      const args = Array.prototype.slice.call(arguments);
      return args.join('');
    };
    
    // good
    const concatenateAll = (...args) => args.join('');

  • 4.6 Use default parameter syntax rather than mutating function arguments.

    // really bad
    const handleThings = opts => {
      // No! We shouldn’t mutate function arguments.
      // Double bad: if opts is falsy it'll be set to an object which may
      // be what you want but it can introduce subtle bugs.
      opts = opts || {};
      // ...
    };
    
    // still bad
    const handleThings = opts => {
      if (opts === void 0) {
        opts = {};
      }
      // ...
    };
    
    // good
    const handleThings = (opts = {}) => {
      // ...
    };

  • 4.8 Never use the Function constructor to create a new function. eslint: no-new-func

    Why? Creating a function in this way evaluates a string similarly to eval(), which opens vulnerabilities.

    // bad
    const add = new Function('a', 'b', 'return a + b');
    
    // still bad
    const subtract = Function('a', 'b', 'return a - b');
    
    // good
    const add = (a, b)  => a + b;
    const subtract = (a, b)  => a - b;

  • 4.9 Never reassign parameters. eslint: no-param-reassign

    Why? Reassigning parameters can lead to unexpected behavior, especially when accessing the arguments object. It can also cause optimization issues, especially in V8.

    // bad
    const f1 = a => {
      a = 1;
      // ...
    }
    // bad
    const f2 = a => {
      if (!a) {
        a = 1;
      }
      // ...
    }
    
    // good
    const f3 = a => {
      const b = a || 1;
      // ...
    }
    // best
    const f4 = (a = 1) => {
      // ...
    }

⬆ back to top

Arrow Functions

  • 5.1 Preferably use arrow functions over regular function expression, unless there is a specific need for it.

    // bad
    function increase() {
      // ...
    }
    
    // good
    function increase() {
      this.counter++;
      // ...
    }
    
    // good
    const increase = () => {
      //...
    };

  • 5.2 When you must use an anonymous function (as when passing an inline callback), use arrow function notation. eslint: prefer-arrow-callback, arrow-spacing

    Why? It creates a version of the function that executes in the context of this, which is usually what you want, and is a more concise syntax.

    Why not? If you have a fairly complicated function, you might move that logic out into its own named function expression.

    // bad
    [1, 2, 3].map(function(x) {
      const y = x + 1;
      return x * y;
    });
    
    // good
    [1, 2, 3].map(x => {
      const y = x + 1;
      return x * y;
    });

    ⬆ back to top

Modules

  • 6.1 Always use modules (import/export) over a non-standard module system. You can always transpile to your preferred module system.

    Why? Modules are the future, let’s start using the future now.

    // bad
    const utils = require('./utils');
    module.exports = utils.es6;
    
    // ok
    import utils from './utils';
    export default utils.es6;
    
    // best
    import { es6 } from './utils';
    export default es6;

  • 6.2 Do not use wildcard imports. Unless needed for specific cases.

    Why? This makes sure you have a single default export.

    // bad
    import * as utils from './utils';
    
    // good
    import utils from './utils';

  • 6.3 Only import from a path in one place. eslint: no-duplicate-imports

    Why? Having multiple lines that import from the same path can make code harder to maintain.

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

  • 6.4 In modules where default export exist prefer a named function

    Why? Function declarations are hoisted, which means that it’s easy - too easy - to reference the function before it is defined in the file. This harms readability and maintainability. If you find that a function’s definition is large or complex enough that it is interfering with understanding the rest of the file, then perhaps it’s time to extract it to its own module! Don’t forget to explicitly name the expression, regardless of whether or not the name is inferred from the containing variable (which is often the case in modern browsers or when using compilers such as Babel). This eliminates any assumptions made about the Error’s call stack. (Discussion)

      // bad
      export default function() {}
      export default () => {}
    
      // good
      const foo = () => {}
      export default foo;

  • 6.5 Put all imports above non-import statements. eslint: import/first

    Why? Since imports are hoisted, keeping them all at the top prevents surprising behavior.

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

    ⬆ back to top

Iterators and Generators

  • 7.1 Don’t use iterators. Prefer JavaScript’s higher-order functions instead of loops like for-in or for-of. eslint: no-iterator no-restricted-syntax

    Why? This enforces our immutable rule. Dealing with pure functions that return values is easier to reason about than side effects.

    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
    let sum = 0;
    numbers.forEach(num => {
      sum += num;
    });
    sum === 15;
    
    // best (use the functional force)
    const sum = numbers.reduce((total, num) => total + num, 0);
    sum === 15;
    
    // bad
    const increasedByOne = [];
    for (let i = 0; i < numbers.length; i++) {
      increasedByOne.push(numbers[i] + 1);
    }
    
    // good
    const increasedByOne = [];
    numbers.forEach(num => {
      increasedByOne.push(num + 1);
    });
    
    // best (keeping it functional)
    const increasedByOne = numbers.map(num => num + 1);

    ⬆ back to top

Properties

  • 8.1 Use dot notation when accessing properties. eslint: dot-notation

    const luke = {
      jedi: true,
      age: 28
    };
    
    // bad
    const isJedi = luke['jedi'];
    
    // good
    const isJedi = luke.jedi;
    
    // good
    const universe = {
      'the-force': true,
    }
    const hasForce = universe['the-force'];

Variables

  • 9.1 Assign variables where you need them, but place them in a reasonable place.

    Why? let and const are block scoped and not function scoped.

    // bad - unnecessary function call
    const checkName = hasName => {
      const name = getName();
    
      if (hasName === 'test') {
        return false;
      }
    
      if (name === 'test') {
        this.setName('');
        return false;
      }
    
      return name;
    }
    
    // good
    const checkName = hasName => {
      if (hasName === 'test') {
        return false;
      }
    
      const name = getName();
    
      if (name === 'test') {
        this.setName('');
        return false;
      }
    
      return name;
    }

  • 9.2 Use uppercase with snake case

    Why? It seperates a final constant from regular variables.

      // bad
      const thisIsAConstant= 'example';
    
      // good
      const THIS_IS_A_CONSTANT = 'example';

    ⬆ back to top

Comparison Operators & Equality

  • 10.1 Use === and !== over == and !=. eslint: eqeqeq

      // bad 
      if (value == 1) {
        // ...
      }
    
      // good 
      if (value === 1) {
        // ...
      }

  • 10.2 Use Boolean not double negative (!!), to cast a value to a boolean

    // bad
    if (!!value) {
      //...
    }
    
    // good
    if (Boolean(value)) {
      //...
    }

  • 10.3 Use shortcuts for booleans, but explicit comparisons for strings and numbers.

    // bad
    if (isValid === true) {
      // ...
    }
    
    // good
    if (isValid) {
      // ...
    }
    
    // bad
    if (name) {
      // ...
    }
    
    // good
    if (name !== '') {
      // ...
    }
    
    // bad
    if (collection.length) {
      // ...
    }
    
    // good
    if (collection.length > 0) {
      // ...
    }

⬆ back to top

Blocks

  • 11.1 Use braces with all multi-line blocks. eslint: nonblock-statement-body-position

    // bad
    if (test) return false;
    
    // bad
    if (test) { return false };
    
    // good
    if (test) {
      return false;
    }

⬆ back to top

Comments

  • 12.1 Use // TODO: to annotate solutions to problems. It should be connected to a story in JIRA.

    // good
    const calculator = () => {
      // TODO: add logic to calculate: ETIWEB-1234
      // or
      // TODO: add logic to calculate: http://jira.url.net/ETIWEB-1234
    };

⬆ back to top

Whitespace

  • 12.1 Leave a blank line after blocks and before the next statement.

    // bad
    if (foo) {
      return bar;
    }
    return baz;
    
    // good
    if (foo) {
      return bar;
    }
    
    return baz;
    
    // bad
    const obj = {
      foo() {
      },
      bar() {
      },
    };
    return obj;
    
    // good
    const obj = {
      foo() {
      },
    
      bar() {
      },
    };
    
    return obj;
    
    // bad
    const arr = [
      foo() {
      },
      bar() {
      },
    ];
    return arr;
    
    // good
    const arr = [
      foo() {
      },
    
      bar() {
      },
    ];
    
    return arr;

    ⬆ back to top

Naming Conventions

  • 13.1 Avoid single letter names. Be descriptive with your naming. eslint: id-length

    // bad
    const q = () => {
      // ...
    };
    
    // good
    const query = () => {
      // ...
    };

  • 13.2 Use camelCase when naming objects, functions, and instances. eslint: camelcase

    // bad
    const OBJEcttsssss = {};
    const this_is_my_object = {};
    const c = () => {}
    
    // good
    const thisIsMyObject = {};
    const thisIsMyFunction = () => {}

  • 13.3 Use PascalCase only when naming constructors, classes or components. eslint: new-cap

    // bad
    function user(options) {
      this.name = options.name;
    }
    
    const bad = new user({
      name: 'nope'
    });
    
    const user = ({ name }) => <h1>{name}</h1>;
    
    // good
    class User {
      constructor(options) {
        this.name = options.name;
      }
    }
    
    const good = new User({
      name: 'yup'
    });
    
    const User = ({ name }) => <h1>{name}</h1>;

  • 13.4 Do not use trailing or leading underscores. eslint: no-underscore-dangle

    Why? 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”, in fact, these properties are 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. tl;dr: if you want something to be “private”, it must not be observably present.

    // bad
    this.__firstName__ = 'Panda';
    this.firstName_ = 'Panda';
    this._firstName = 'Panda';
    
    // good
    this.firstName = 'Panda';
    
    // This is private
    const doStuff = x => x * 1;
    
    const myFunc = a => doStuff(a);
    
    export default myFunc

  • 13.5 Don’t save references to this. Use arrow functions or Function#bind.

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

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

    // file 1 contents
    class CheckBox {
      // ...
    }
    export default CheckBox;
    
    // file 2 contents
    const fortyTwo = () => 42;
    export default fortyTwo;
    
    // file 3 contents
    const insideDirectory = () => {}
    export default 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

License

(MIT License)

Copyright (c) 2018 Etraveli Technology

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Amendments

This style guide was heavily inspired by Airbnb's guide: https://github.com/airbnb/javascript

⬆ back to top

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published