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
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
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
⬆ back to top
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;
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;
}
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.
⬆ back to top
3.1 Use the literal syntax for object creation. eslint: no-new-object
// bad
const item = new Object();
// good
const item = {};
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,
};
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;
},
};
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,
};
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,
};
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,
};
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
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 }
⬆ back to top
4.1 Use the literal syntax for array creation. eslint: no-array-constructor
// bad
const items = new Array();
// good
const items = [];
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');
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];
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];
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);
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);
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 `acc` becomes undefined after the first iteration
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
const flatten = acc.concat(item);
});
// good
[[0, 1], [2, 3], [4, 5]].reduce((acc, item, index) => {
const flatten = acc.concat(item);
return flatten;
});
// bad
inbox.filter((msg) => {
const { subject, author } = msg;
if (subject === 'Mockingbird') {
return author === 'Harper Lee';
} else {
return false;
}
});
// good
inbox.filter((msg) => {
const { subject, author } = msg;
if (subject === 'Mockingbird') {
return author === 'Harper Lee';
}
return false;
});
4.8 Use line breaks after opening array brackets and before closing array brackets, if an array has multiple lines
// bad
const arr = [
[0, 1], [2, 3], [4, 5],
];
const objectInArray = [{
id: 1,
}, {
id: 2,
}];
const numberInArray = [
1, 2,
];
// good
const arr = [[0, 1], [2, 3], [4, 5]];
const objectInArray = [
{
id: 1,
},
{
id: 2,
},
];
const numberInArray = [
1,
2,
];
⬆ back to top
5.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, and from repetitive access of the object. Repeating object access creates more repetitive code, requires more reading, and creates more opportunities for mistakes. Destructuring objects also provides a single site of definition of the object structure that is used in the block, rather than requiring reading the entire block to determine what is used.
// 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}`;
}
5.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;
5.3 Use object destructuring for multiple return values, not array destructuring.
Why? You can add new properties over time or change the order of things without breaking call sites.
// 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);
⬆ back to top
6.1 Use single quotes ''
for strings. eslint: quotes
// bad
const name = "Capt. Janeway";
// bad - template literals should contain interpolation or newlines
const name = `Capt. Janeway`;
// good
const name = 'Capt. Janeway';
6.2 Strings that cause the line to go over 100 characters should not be written across multiple lines using string concatenation.
Why? Broken strings are painful to work with and make code less searchable.
// bad
const errorMessage = 'This is a super long error that was thrown because
of Batman. When you stop to think about how Batman had anything to do
with this, you would get nowhere
fast.';
// bad
const errorMessage = 'This is a super long error that was thrown because ' +
'of Batman. When you stop to think about how Batman had anything to do ' +
'with this, you would get nowhere fast.';
// good
const errorMessage = 'This is a super long error that was thrown because of Batman. When you stop to think about how Batman had anything to do with this, you would get nowhere fast.';
6.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
function sayHi(name) {
return 'How are you, ' + name + '?';
}
// bad
function sayHi(name) {
return ['How are you, ', name, '?'].join();
}
// bad
function sayHi(name) {
return `How are you, ${ name }?`;
}
// good
function sayHi(name) {
return `How are you, ${name}?`;
}
eval()
on a string; it opens too many vulnerabilities. eslint: no-eval
6.5 Do not unnecessarily escape characters in strings. eslint: no-useless-escape
Why? Backslashes