JavaScript Best Practices

Anh-Thi Dinh

References

Special characters

1// Do
2const units = 'μs';
1// Don't
2const units = '\u03bcs'; // Greek letter mu, 's'
1// Do
2const output = '\ufeff' + content;  // byte order mark
1// Don't (no idea what this is)
2const output = '\ufeff' + content;

Modules

Avoid using default exports, as they allow inconsistent naming during import.
1// Do
2export class Foo {...}
3
4// Or
5class Foo {...}
6export { Foo };
1// Don't
2export default class Foo {...}

Formatting

Empty blocks,
1// Do
2function doNothing() {}
1// Don't
2function doNothing() {
3	// ...
4}
Where to break line → a higher syntactic level,
1// Do
2const currentEstimate =
3    calc(currentEstimate + x * currentEstimate) /
4        2.0;
1// Don't
2const currentEstimate = calc(currentEstimate + x *
3    currentEstimate) / 2.0;
Horizontal alignment: discouraged.
1// Do
2{
3  tiny: 42, // this is great
4  longer: 435, // this too
5};
1// Don't
2{
3  tiny:   42,  // permitted, but future edits
4  longer: 435, // may leave it unaligned
5};

JSDoc

Using markdown,
1// Do
2/**
3 * Computes weight based on three factors:
4 *
5 *  - items sent
6 *  - items received
7 *  - last timestamp
8 */
1// Don't (it will appear as a single line instead)
2/**
3 * Computes weight based on three factors:
4 *   items sent
5 *   items received
6 *   last timestamp
7 */

String

No line continuations,
1// Do
2const longString = 'This is a very long string that far exceeds the 80 ' +
3    'column limit. It does not contain long stretches of spaces since ' +
4    'the concatenated strings are cleaner.';
1// Don't
2const longString = 'This is a very long string that far exceeds the 80 \
3    column limit. It unfortunately contains long stretches of spaces due \
4    to how the continued lines are indented.';

Array

Create a new array,
1// Do
2const arr = [];
1// Don't
2const arr = new Array(length);
When adding new item to an array (given const arr = [];),
1// Do
2arr.push(1);
1// Don't
2arr[arr.length] = 1;

Comments

1// Do: leave a space
1//Don't: no space
Add an empty line before a comment. Comments should describe the purpose of the following code block.
1function checkout(goodsPrice, shipmentPrice, taxes) {
2  // Calculate the total price
3  const total = goodsPrice + shipmentPrice + taxes;
4
5  // Create and append a new paragraph to the document
6  const para = document.createElement("p");
7  para.textContent = `Total price is ${total}`;
8  document.body.appendChild(para);
9}
1// Do
2function exampleFunc(fruitBasket) {
3  console.log(fruitBasket); // ['banana', 'mango', 'orange']
4}
5
6// Or do (if it's too long)
7function exampleFunc(fruitBasket) {
8  console.log(fruitBasket);
9  // ['banana', 'mango', 'orange', 'apple', 'pear', 'durian', 'lemon']
10}
1// Don't
2function exampleFunc(fruitBasket) {
3  // Logs: ['banana', 'mango', 'orange']
4  console.log(fruitBasket);
5}
Multi-line comments
1// Do
2// Comment can be in 
3// multiple lines
1/* Don't
2   Use for multiple line 
3   like this */
But we use /* */ in below case,
1array.forEach((value /* , index, array */) => {
2  // …
3});

Functions

Use camelCase for function names,
1// Do
2function sayHello() {
3  console.log("Hello!");
4}
1// Don't
2function SayHello() {
3  console.log("Hello!");
4}
5
6function doIt() {
7  console.log("Hello!");
8}
Use function declaration over function expression or arrow function,
1// Do
2function sum(a, b) {
3  return a + b;
4}
1// Don't
2let sum = function (a, b) {
3  return a + b;
4};
5
6// Don't
7const x = () => {
8  // …
9};
Use arrow function in callback,
1// Do
2const array1 = [1, 2, 3, 4];
3const sum = array1.reduce((a, b) => a + b);
1// Don't
2const array1 = [1, 2, 3, 4];
3const sum = array1.reduce(function (a, b) {
4  return a + b;
5});
6
When using arrow functions, use implicit return when possible,
1// Do
2arr.map((e) => e.id);
1// Don't
2arr.map((e) => {
3  return e.id;
4});

Loops

Prefer to use for...of and forEach instead of classical for (;;)
1// Do
2for (const dog of dogs) {
3  console.log(dog);
4}
5
6// or
7dogs.forEach((dog) => {
8  console.log(dog);
9});
1// Don't
2for (let i = 0; i < dogs.length; i++) {
3  console.log(dogs[i]);
4}
 
Use const in for...of and use let in other loops.
When you need to consider index,
1// Do
2gerbils.forEach((gerbil, i) => {
3  console.log(`Gerbil #${i}: ${gerbil}`);
4});
1// Don't
2for (let i = 0; i < gerbils.length; i++) {
3  console.log(`Gerbil #${i}: ${gerbils[i]}`);
4}

Control statements

Continue right after if, don’t use else
1// Do
2if (condition) {
3	// actions when true
4}
5// actions when false
1// Don't
2if (condition) {
3	// actions when true
4} else {
5	// actions when false
6}
Use braces for control flows and loops,
1// Do
2for (const car of storedCars) {
3  car.paint("red");
4}
1// Don't
2for (const car of storedCars) car.paint("red");
Don’t add break after return in switch
1// Do
2switch (species) {
3  case "chicken":
4    return farm.shed;
5  case "horse":
6    return corral.entry;
7  default:
8    return "";
9}
1// Don't
2switch (species) {
3  case "chicken":
4    return farm.shed;
5    break;
6  case "horse":
7    return corral.entry;
8    break;
9  default:
10    return "";
11}
Use default as the last case, don’t use break with it.
Use brackets {} if you define a local variable inside each case.
1switch (fruits) {
2  case "Orange": {
3    const slice = fruit.slice();
4    eat(slice);
5    break;
6  }
7  case "Apple": {
8    const core = fruit.extractCore();
9    recycle(core);
10    break;
11  }
12}

Error handling

Use try...catch to catch error,
1try {
2  console.log(getResult());
3} catch (e) {
4  console.error(e);
5}
👍 Only recoverable errors should be catch!

Objects

Use PascalCase to define a class name and camelCase for the object property, methods and instances.
Use lirerals, not constructors to create a new object,
1// Do
2const obj = {}
1// Don't
2const obj = new Object()
Use method definition syntax,
1// Do
2const obj = {
3  foo() {
4    // …
5  },
6  bar() {
7    // …
8  },
9};
1// Don't
2const obj = {
3  foo: function () {
4    // …
5  },
6  bar: function () {
7    // …
8  },
9};
Use shorthand,
1// Do
2function createObject(name, age) {
3  return { name, age };
4}
1// Don't
2function createObject(name, age) {
3  return { name: name, age: age };
4}
Don’t mix quoted and unquoted keys,
1// Do
2const { width: 42, maxWidth: 43 };
1// Don't
2const { width: 42, 'maxWidth': 43 };

Operators

Prefer to use conditional ternary operator,
1// Do
2const x = condition ? 1 : 2;
 
1// Don't
2let x;
3if (condition) {
4  x = 1;
5} else {
6  x = 2;
7}
Use strict equality operator,
1// Do
2name === "Thi";
3age !== 25;
1// Don't
2name == "Shilpa";
3age != 25;
Use shortcuts for boolean tests,
1// Do
2if (x)
3
4// Or 
5if (!y)
1// Don't
2if (x === true)
3
4// or don't
5if (y === false)
1// Do
2const name = company?.group?.team
3const firstElement = list?.[0]
1// Don't
2let name;
3if (company && company.group && company.group.team) {
4	name = company.group.team;
5}

Variables

Naming,
1// Do
2const accelerometer = ...
3const cars = ...
4const a = ...
5const currencyName = ...
6const car = ...
1// Don't
2const accmtr = ...
3const carArray = ...
4const descriptiveVariable = ...
5const currency_name = ...
6const aCar = ...
1// Do
2const playerScore = 0;
3const speed = distance / time;
1// Don't
2const thisIsaveryLONGVariableThatRecordsPlayerscore345654 = 0;
3const s = d / t;
Use let or const, not var to declare a new variable!
Declare one variable per line,
1// Do
2let var1;
3let var2;
4let var3 = 1;
5let var4 = 1;
1// Don't
2let var1, var2;
3let var3 = var4 = 1;
Void implicit type coercions,
1// Do
2class Person {
3  #name;
4  #birthYear;
5
6  constructor(name, year) {
7    this.#name = String(name); // 👈
8    this.#birthYear = Number(year); // 👈
9  }
10}
1// Don't
2class Person {
3  #name;
4  #birthYear;
5
6  constructor(name, year) {
7    this.#name = "" + name; // ✋
8    this.#birthYear = +year; // ✋
9  }
10}
To be continued…