This style guide is for general JavaScript conventions and styles that we follow across all our JavaScript. For Angular specific conventions, refer to the Angular Style Guide.
The projects we work on support the following browsers:
- Chrome 39+
- Safari 7+
- Firefox 33+
- IE10+
Be aware of this when reading the guide - some of the JS shown here may not work in older versions.
Why: in JavaScript there is no difference between single and double quotes. Rather than have a mix throughout a code base, pick one and stick to it.
Why: this encourages developers to write lines that do less, and extract variables and functions where a line is longer than required.
We don’t have an 80 char hard limit because sometimes it’s more readable to let a line be 85 characters rather than break it up, but in general you should aim for 80 characters or less.
Why: it's intent is much clearer than indexOf
, and it is included as part of the ES6 specification
In a JS project that is transpiled using Traceur, Babel or similar, use the new String.prototype.includes
method:
// bad
if (str.indexOf('foo') > -1) {
}
// good
if (str.includes('foo')) {
}
Why: ensures that errors are always handled.
// bad
doSomething().then(...)
// good
doSomething().then(...).catch(...)
Why: this keeps promise chains easier to read, and keeps callback functions smaller.
// bad
doSomething().then(function processSomething(data) {
// do something
// do something else
// do something else else
});
// good
doSomething().then(function processSomething(data) {
// do something
return data;
}).then(function somethingElse(data) {
// do something else
return data;
}).then(function anotherThing(data) {
// and so on
});
Why: the function name replaces “anonymous function” in stack traces, which makes debugging easier. Naming functions also helps code be self documenting.
// bad
createUser().then(function() {
});
// good
createUser().then(function userCreatedSuccesfully() {
});
Why: function declarations are easier to pick out in larger code files.
Why: hoisting enables JavaScript files to have their most important content at the top. However, we’re used to reading top to bottom and left to right, so this can be confusing.
// this will work but is unclear
thing();
// more code
function thing() {
}
Why: indenting an entire function body makes it harder to read. Providing a base case early keeps the code cleaner.
// bad
function doSomethingOnOddNumbers(x) {
if (x % 2 === 1) {
// do stuff
}
};
// good
function doSomethingOnOddNumbers(x) {
if (x % 2 === 0) return;
// do stuff
}
// bad
function doSomething () {
}
// bad
function doSomething(){
}
// good
function doSomething() {
}
When a function has arguments that make the line longer than ~80 characters, split it into one parameter per line.
Why: keeps lines below 80 characters, after which readability declines.
// good
function SomeController(foo, bar, baz) {
}
// bad
function SomeController(foo, bar, baz, abc, def, ghi, jkl, mno, pqr, stu, vwx, yz) {
}
// good
function SomeController(
foo,
bar,
baz,
abc,
def,
ghi,
jkl,
mno,
pqr,
stu,
vwx,
yz
) {
}
When an object literal has more than one property, split it into one property per line, leaving a trailing comma.
Why: keeps lines below 80 characters, after which readability declines. Trailing commas make it easier to reorder existing lines without editing them, and makes new lines clearer in the diff.
// good
{ foo: 2 }
// bad
{ foo: 2, bar: 3 }
// bad
{
foo: 2,
bar: 3
}
// good
{
foo: 2,
bar: 3,
}
Why: avoids unnecessary function wrapping.
function isOdd(x) {
return x % 2 === 1;
}
// bad
[1, 2, 3].filter(function(x) {
return isOdd(x);
});
// good
[1, 2, 3].filter(isOdd);
Why: JavaScript's object mutation can be very implicit and easy to miss. It's much better to have slightly more verbose but much clearer code.
// bad - payments array has been mutated, not obvious
payments.forEach(function(payment) {
payment.id = 'ABC123'
});
// bad - the original payments array is mutated
payments.map(function(payment) {
payment.id = 'ABC123';
});
// good - don't mutate the array, but create and modify a new one:
payments = _.cloneDeep(payments).map(function(payment) {
payment.id = 'ABC123';
return payment;
});
In practice we may not use _.cloneDeep
- alter the above code as appropriate based on the libraries available.
Why: it’s easier to reorder items and the diff is cleaner when a new item is added.
// bad
[
'jack',
'max'
]
// good
[
'jack',
'max',
]
Why: its intent is much clearer than using indexOf
.
If LoDash is available and included in the project, _.includes
is much easier to read and cleaner to use than indexOf
.
// bad
if (names.indexOf('jack') > -1) {
}
// good
if (_.includes(names, 'jack')) {
}
Why: it’s unclear where the variable is defined.
// bad
if (x) {
var y = 2;
} else {
var y = 3;
}
// good
var y;
if (x) {
y = 2;
} else {
y = 3;
}
Why: ternary operators are concise when used with small conditionals.
Use an if
statement if any branch of the condition is complex.
var y = x ? 2 : 3;
Why: adding a new variable does not mean other lines need to be edited, and existing lines can be reordered easily.
// bad
var x, y, z;
// good
var x;
var y;
var z;
Why: eliminates errors caused by JavaScript’s complex equality and coercion rules.
Why: using ==
checks for both undefined
and null
, whereas ===
only checks for one.
// bad
if (x === undefined || x === null) {}
// good
if (x == null) {}
Why: keys that have a falsey value but are still present might result in false positives.
var thing = { count: 0 };
if (!thing.count) // will evaluate to true, but we don’t want it to
if (!thing.hasOwnProperty('count')) // will evaluate to false, which is what we want
Why: conditionals without braces over multiple lines can easily be misconstrued.
// bad
if (something) { x = true }
// good
if (something) x = true;
// bad
if (something)
doSomethingElse();
doAnotherThing(); // <- not part of the conditional!
// good
if (something) {
doSomethingElse();
doAnotherThing();
}
Why: makes it easier to pick out if
s in a large code file.
// bad
if(foo)
// good
if (foo)
Prefer “fat arrow” functions when the function is small enough to fit on one line, or when the function’s job is to return the result of an expression.
Why: they are more concise and readable, and implicitly return when the body is an expression.
// bad
[1, 2, 3].map(function(x) { return x * 2 });
// good
[1, 2, 3].map((x) => x * 2);
Why: an arrow function that spans multiple lines requires a block and an explicit return statement. Therefore, using an arrow function offers a minimally shorter expression at the expense of losing the function name in a stack trace.
// bad
[1, 2, 3].map((x) => {
// do lots of things
// do more things
return x * 2;
});
// good
[1, 2, 3].map(function(x) {
// do lots of things
// do more things
return x * 2;
});
Why: they aren’t needed when the function only takes one parameter, but it’s clearer to always include them.
// bad
[1, 2, 3].map(x => x * 2);
// good
[1, 2, 3].map((x) => x * 2);
Unfortunately our e2e tests run in Node.js, so don't support arrow functions (yet). When they do, we'll prefer to use them there too.
Why: keeps tests cleaner and easier to pick out the actual assertions.
// bad
it('does a thing', function() {
});
// good
it('does a thing', () => {
});
Why: easier to read.
// bad
var fullName = firstName + ' ' + lastName;
// good
var fullName = `${firstName} ${lastName}`;
// bad
function foo() {
var args = [].slice.call(arguments);
}
// good
function foo(...args) {
}
Use relative paths when the target is a descendant of the path declaration, else prefer absolute paths.
Why: easier to see a file’s position, and prevents many parent directory operators.
// bad
import {foo} from '../../service/foo';
// good
import {foo} from 'client/app/service/foo';
// good
import {foo} from './foo';
Use them! Prefer const
by default.
const apiUrl = 'http://example.com';