A mostly reasonable approach to JavaScript
Note: this guide assumes you are using Babel, and requires that you use babel-preset-airbnb or the equivalent. It also assumes you are installing shims/polyfills in your app, with airbnb-browser-shims or the equivalent.
This guide is available in other languages too. See Translation
Other Style Guides
<a name="types--primitives"></a><a name="1.1"></a>
1.1 Primitives: When you access a primitive type you work directly on its value.
string
number
boolean
null
undefined
symbol
bigint
const foo = 1; let bar = foo; bar = 9; console.log(foo, bar); // => 1, 9
<a name="types--complex"></a><a name="1.2"></a>
1.2 Complex: When you access a complex type you work on a reference to its value.
object
array
function
const foo = [1, 2]; const bar = foo; bar[0] = 9; console.log(foo[0], bar[0]); // => 9, 9
<a name="references--prefer-const"></a><a name="2.1"></a>
2.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 = 2; // good const a = 1; const b = 2;
<a name="references--disallow-var"></a><a name="2.2"></a>
2.2 If you must reassign references, use let
instead of var
. 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; }
<a name="references--block-scope"></a><a name="2.3"></a>
2.3 Note that both let
and const
are block-scoped, whereas var
is function-scoped.
// const and let only exist in the blocks they are defined in. { let a = 1; const b = 1; var c = 1; } console.log(a); // ReferenceError console.log(b); // ReferenceError console.log(c); // Prints 1
In the above code, you can see that referencing a
and b
will produce a ReferenceError, while c
contains the number. This is because a
and b
are block scoped, while c
is scoped to the containing function.
<a name="objects--no-new"></a><a name="3.1"></a>
3.1 Use the literal syntax for object creation. eslint: no-new-object
// bad const item = new Object(); // good const item = {};
<a name="es6-computed-properties"></a><a name="3.4"></a>
3.2 Use computed property names when creating objects with dynamic property names.
Why? They allow you to define all the properties of an object in one place.
function getKey(k) { return `a key named ${k}`; } // bad const obj = { id: 5, name: 'San Francisco', }; obj[getKey('enabled')] = true; // good const obj = { id: 5, name: 'San Francisco', [getKey('enabled')]: true, };
<a name="es6-object-shorthand"></a><a name="3.5"></a>
3.3 Use object method shorthand. eslint: object-shorthand
// bad const atom = { value: 1, addValue: function (value) { return atom.value + value; }, }; // good const atom = { value: 1, addValue(value) { return atom.value + value; }, };
<a name="es6-object-concise"></a><a name="3.6"></a>
3.4 Use property value shorthand. eslint: object-shorthand
Why? It is shorter and descriptive.
const lukeSkywalker = 'Luke Skywalker'; // bad const obj = { lukeSkywalker: lukeSkywalker, }; // good const obj = { lukeSkywalker, };
<a name="objects--grouped-shorthand"></a><a name="3.7"></a>
3.5 Group your shorthand properties at the beginning of your object declaration.
Why? It’s easier to tell which properties are using the shorthand.
const anakinSkywalker = 'Anakin Skywalker'; const lukeSkywalker = 'Luke Skywalker'; // bad const obj = { episodeOne: 1, twoJediWalkIntoACantina: 2, lukeSkywalker, episodeThree: 3, mayTheFourth: 4, anakinSkywalker, }; // good const obj = { lukeSkywalker, anakinSkywalker, episodeOne: 1, twoJediWalkIntoACantina: 2, episodeThree: 3, mayTheFourth: 4, };
<a name="objects--quoted-props"></a><a name="3.8"></a>
3.6 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, };
<a name="objects--prototype-builtins"></a>
3.7 Do not call Object.prototype
methods directly, such as hasOwnProperty
, propertyIsEnumerable
, and isPrototypeOf
. eslint: no-prototype-builtins
Why? These methods may be shadowed by properties on the object in question - consider
{ hasOwnProperty: false }
- or, the object may be a null object (Object.create(null)
). In modern browsers that support ES2022, or with a polyfill such as https://npmjs.com/object.hasown,Object.hasOwn
can also be used as an alternative toObject.prototype.hasOwnProperty.call
.
// bad console.log(object.hasOwnProperty(key)); // good console.log(Object.prototype.hasOwnProperty.call(object, key)); // better const has = Object.prototype.hasOwnProperty; // cache the lookup once, in module scope. console.log(has.call(object, key)); // best console.log(Object.hasOwn(object, key)); // only supported in browsers that support ES2022 /* or */ import has from 'has'; // https://www.npmjs.com/package/has console.log(has(object, key)); /* or */ console.log(Object.hasOwn(object, key)); // https://www.npmjs.com/package/object.hasown
<a name="objects--rest-spread"></a>
3.8 Prefer the object spread syntax over Object.assign
to shallow-copy objects. Use the object rest parameter syntax to get a new object with certain properties omitted. eslint: prefer-object-spread
// 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 }
<a name="arrays--literals"></a><a name="4.1"></a>
4.1 Use the literal syntax for array creation. eslint: no-array-constructor
// bad const items = new Array(); // good const items = [];
<a name="arrays--push"></a><a name="4.2"></a>
4.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');
<a name="es6-array-spreads"></a><a name="4.3"></a>
4.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];
<a name="arrays--from"></a> <a name="arrays--from-iterable"></a><a name="4.4"></a>
4.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];
<a name="arrays--from-array-like"></a>
4.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);
<a name="arrays--mapping"></a>
4.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);
<a name="arrays--callback-return"></a><a name="4.5"></a>
4.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 8.2. eslint: array-callback-return
// good [1, 2, 3].map((x) => { const y = x + 1; return x * y; }); // good [1, 2, 3].map((x) => x + 1); // bad - no returned value means