This is how we write Javascript code.
Other Style Guides
- References
- Objects
- Arrays
- Destructuring
- Strings
- Functions
- Arrow Functions
- Modules
- Iterators and Generators
- Properties
- Variables
- Comparison Operators & Equality
- Blocks
- Comments
- Whitespace
- Naming Conventions
-
1.1 Use
const
for all of your references; avoid usingvar
. 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 ofvar
. eslint:no-var
Why?
let
is block-scoped rather than function-scoped likevar
.// bad var count = 1; if (true) { count += 1; } // good, use the let. let count = 1; if (true) { count += 1; }
-
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 }
-
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 ofArray.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 );
-
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;
-
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}?`;
-
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 likearguments
.// 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) => { // ... }
-
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; });
-
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
import
s above non-import statements. eslint:import/first
Why? Since
import
s 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();
-
7.1 Don’t use iterators. Prefer JavaScript’s higher-order functions instead of loops like
for-in
orfor-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, andObject.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);
-
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'];
-
9.1 Assign variables where you need them, but place them in a reasonable place.
Why?
let
andconst
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';
-
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) { // ... }
-
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; }
-
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 };
-
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;
-
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
(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.
This style guide was heavily inspired by Airbnb's guide: https://github.com/airbnb/javascript