Categories
JavaScript Best Practices

JavaScript Best Practices — Spaces, Regex, and Errors

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

Space Before Function Parenthesis

No space before function parenthesis is more conventional.

It makes sense because it doesn’t add much value.

function foo (x) {
  // ...
}

isn’t much different from:

function foo(x) {
  // ...
}

So we just live with the latter.

Spaces Inside of Parentheses

Spaces inside of parentheses don’t make much sense.

If we have:

foo( 'bar');

or:

foo('bar' );

Then that looks weird.

Instead, we write:

foo('bar');

Spacing Around Infix Operators

We should put spacing around infix operators to make them easier to read.

For instance, instead of writing:

var sum = 1+1;

We write:

var sum = 1 + 1;

Use the Strict Mode Directive

We should use strict mode to prevent mistakes.

Modules have strict mode enabled by default.

For instance, instead of writing:

function foo() {
  //...
}

We write:

"use strict";

function foo() {
  //...
}

We just put it on top so that it’s applied everywhere.

Spacing Around Colons of switch Statements

We should have conventional spacing around colons in switch statements.

For instance, instead of writing:

switch (a) {
  case 0: break;
  default: foo();
}

We write:

switch (a) {
  case 0:
    break;
  default:
    foo();
}

Symbol Description

Symbols should have descriptions so we know what we’re looking at.

For instance, instead of writing:

const foo = Symbol();

We write:

const foo = Symbol("description");

Usage of Spacing in Template Strings

We should follow conventional spacing ion template strings.

The expressions don’t need a space between them.

For instance, instead of writing:

`hello, ${ person.name}`;

We write:

`hello, ${person.name}`;

Spacing Between Template Tags and their Literals

There’s usually no space between template tags and their literals.

For instance, instead of writing;

let baz = func `foo bar`;

We write:

let baz = func`foo bar`;

Simplify Regexes by Making them Shorter, Consistent, and Safer

If there’s a shorter version of a pattern, then we should use it to create our regex.

For instance, instead of writing:

const regex = /[0-9]/;
const regex = /[^0-9]/;

We write:

const regex = /d/;
const regex = /D/;

to get digits.

And instead of writing:

const regex = /[a-zA-Z0-9_]/;
const regex = /[a-z0-9_]/i;

We write:

const regex = /w/;
const regex = /w/i;

to match alphanumeric characters.

And instead of writing:

const regex = /[^a-zA-Z0-9_]/;
const regex = /[^a-z0-9_]/i;

We write:

const regex = /W/;
const regex = /W/i;

to get nonalphanumeric characters.

Specific Parameter Name in catch Clauses

A specific parameter name makes more sense than different ones in catch clauses.

This way, we won’t be confused.

For instance, instead of writing:

try {} catch (badName) {}

We write:

try {} catch (error) {}

Move Function Definitions to the Highest Possible Scope

We move function definitions to the highest possible scope so that they can be better optimized and improve readability.

For instance, instead of writing;

export function doWork(foo) {
  function doBar(bar) {
    return bar === 'bar';
  }
  return doBar;
}

We return a function but don’t capture anything from the doWork ‘s scope.

Instead, we write:

function doBar(bar) {
  return bar === 'bar';
}

Enforce Correct Error Subclassing

The only way to create a subclass from an Error class is to use the extends keyword.

And we don’t pass in message to the parent constructor since it’s already set in the super call.

For instance, instead of writing:

class SuperError extends Error {
  constructor(message) {
    super(message);
    this.message = message;
    this.name = 'SuperError';
  }
}

We write:

class SuperError extends Error {
  constructor(message) {
    super(message);
    this.message = message;
    this.name = 'SuperError';
  }
}

Passing an Message Value when Throwing a Built-in Error

We can pass a message value to the Error constructor to make the error clearer.

For instance, instead of writing:

throw Error();

We write:

throw Error('Foo');

Conclusion

We can use conventional spacing to make our code clearer.

Also, we can create our own Error class.

If we create an Error instance, we should pass in an object to make them clearer.

If there are shorter patterns available for creating a regex, we should use it.

Categories
JavaScript Best Practices

JavaScript Best Practices — The Basics

Like any kind of apps, JavaScript apps also have to be written well.

Otherwise, we run into all kinds of issues later on.

In this article, we’ll look at some best practices we should follow when writing JavaScript code.

Make the Code Understandable

We should make our code understandable.

Variable and function names should be descriptive so we can understand it.

For example, instead of writing:

x y z

We write:

numApples numOranges

We should also write names that makes sense anywhere.

For example, instead of writing:

isOver21()

We write:

isDrinkingAge()

No Global Variables

We shouldn’t use global variables to share code.

The names can easily clash and they may be overwritten by anything.

Therefore, instead of writing:

var current = null;
var labels = {
  foo:'home',
  bar:'articles',
  baz:'contact'
};
function init(){
};
function show(){
   current = 1;
};
function hide(){
   show();
};

We write:

module = function(){
   const current = null;
   const labels = {
     foo:'home',
     bar:'articles',
     baz:'contact'
   };
   const init = function(){
   };
   const  show = function(){
      current = 100;
   };
   const hide = function(){
      show();
   }
   return{ init, show, current }
}();

We put everything in a function.

Stick to a Strict Coding Style

If we stick to a strict style, then we won’t have any problems when it’s worked on by any development.

The code will also work in any environment.

We can use ESLint to make the styles automatically consistent.

Comment as Much as Needed but Not More

We can put comments for things that aren’t described by the code.

Also, we should use /* */ for comments since they work anywhere.

We can comment out code to debug with /* */ :

/*
   var init = function(){
   };
   var show = function(){
      current = 1;
   };
   var hide = function(){
      show();
   }
*/

Avoid Mixing with Other Technologies

If we’re styling, we should use CSS as much as possible.

If we need to style things dynamically, we can add or remove the classes as we wish.

For instance, instead of writing:

const f = document.getElementById('mainform');
const inputs = f.getElementsByTagName('input');
for (let i = 0; i < inputs.length; i++) {
  if (inputs[i].className === 'required' && inputs.value === '') {
    inputs[i].style.borderColor = '#f00';
    inputs[i].style.borderStyle = 'solid';
    inputs[i].style.borderWidth = '1px';
  }
}

We move the styles to the CSS:

.required {
  border: 1px solid #f00
}

And in the JavaScript, we write:

const f = document.getElementById('mainform');
const inputs = f.getElementsByTagName('input');
for (let i = 0; i < inputs.length; i++) {
  if (inputs.value === '') {
    inputs[i].className = 'required';
  }
}

Use Shortcut Notations

We should use shorter notation if it’s abailable.

They don’t impact readabnility and saves typing.

For example, instead of writing:

let food = new Array();
food[0]= 'pork';
food[1]= 'lettuce';
food[2]= 'rice';
food[3]= 'beef';

We write:

let food = [
  'pork',
  'lettuce',
  'rice',
  'beef'
]

It’s short and clear.

Instead of writing:

let x;
if (v){
  x = v;
} else {
  x = 100;
}

We write:

let x = v || 10;

And instead of writing:

let direction;
if(x > 200){
   direction = 1;
} else {
   direction = -1;
}

We write:

let direction = (x > 200) ? 1 : -1;

Modularize

Our code should be modularized.

We can divide them into modules by exporting what other modules will use and importing the needed items.

For example, we can write:

foo.js

export const numApples = 1;

and:

import { numApples } from './foo';

This way, we keep private items and private and expose what we need.

Also, the code is divided into smaller files.

Enhance Progressively

We shouldn’t create lots of JavaScript dependent code.

If we don’t need to manipulate the DOM, then we shouldn’t do it.

Instead, we use CSS for styles as much as we can.

Allow for Configuration and Translation

We should keep shared code in one place so that we can change them easily.

For instance, we can keep them all in a module:

export const classes = {
  current: 'current',
  scrollContainer: 'scroll'
}
export const IDs = {
  maincontainer: 'carousel'
}
export const labels = {
  previous: 'back',
  next: 'next',
  auto: 'play'
}
export const setting = {
  amount: 5,
  skin: 'blue',
  autoplay: false
}
export const init = function() {};
export const scroll = function() {};
export const highlight = function() {};

Conclusion

We should keep code in shared modules and reduce JavaScript operations wherever we can.

Also, we should make code understandable and organized.

Categories
JavaScript Best Practices

JavaScript Best Practices — Substrings, Trimming, Generators

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

Use String#slice() Instead of String#substr() and String#substring()

The slice method can do what the substr or substring method does except it’s shorter.

It’s also more consistent with arrays.

For instance, instead of writing:

foo.substr(start, end);
foo.substring(start, end);

We write:

foo.slice(start, end);

Use .textContent over .innerText

textContent is faster and more predictable than innerText , so we should use that instead.

textContent can also have CDATA and processing instructions.

It’ll also concatenate the textContent of all the nodes.

So instead of writing:

foo.innerText = 'foo';

We write:

foo.textContent = 'foo';

Use String#trimStart() / String#trimEnd() Instead of String#trimLeft() / String#trimRight()

trimStart and trimEnd are more consistent with the other methods like padStart or padEnd .

trimStart trims the whitespace from the start of a string.

And trimEnd trims the whitespace from the end of the string.

So we should use trimStart or trimEnd

For instance, instead of writing:

str.trimLeft();
str.`trimRight`();

We write:

str.trimStart();
str.`trimEnd`();

Throwing TypeError in Type Checking Conditions

If we may get data wit invalid data types, then we may want to throw type errors to make them very obvious.

For instance, instead of writing:

if (!Array.isArray(bar)) {
  throw new Error('not an array');
}

We write:

if (!Array.isArray(bar)) {
  throw new TypeError('not an array');
}

TypeError is specifically thrown when data types don’t match.

No Abbreviations

If the full names aren’t too long, then we shouldn’t abbreviate them.

For instance, insteaf of writing:

class Btn {}

We write:

class Button {}

And instead of writing:

const err = new Error();

We write:

const err = new Error();

Use new When Throwing an Error

Error is a constructor, so we should invoke it with new .

For instance, instead of writing:

throw Error();

We write:

throw new Error();

Use isNaN() to Check for NaN

isNaN is a function that’s specifically made to check for NaN .

We need this since we can’t use === or !== to check it.

For instance:

if (bar === NaN) {
  // ...
}

won’t work since NaN doesn’t equal itself.

Instead, we write:

if (isNaN(bar)) {
  // ...
}

to do the correct comparison.

Other comparison operators also don’t work like we expect with NaN .

Compare typeof Expressions Against Valid Strings

The typeof operator returns a string with the data type name, so we should be comparing against strings.

For instance, instead of writing things like

typeof bar === "strnig"
typeof bar === "undefibed"

We write:

typeof bar === "string"
typeof bar === "undefined"

Variable Declarations to be at the Top of Their Scope

We can put variable declarations at the top of their scope so w can find them more easily.

For instance, instead of writing:

function doWork() {
  var foo;
  if (true) {
    foo = 'abc';
  }
  var bar;
}

We write:

function doWork() {
  var foo;
  var bar;
  if (true) {
    foo = 'abc';
  }
}

so that we can find them all in one place.

IIFEs should be Wrapped

We need to wrap IIFEs to make the JavaScript interpreter parse it as an expression rather than a declaration.

For instance, instead of writing:

let z = function() {
  return {
    y: 'abc'
  };
}();

We write:

let z = (function() {
  return {
    y: 'abc'
  };
})();

The 2nd example will be declared as an expression.

Regex Literals should be Wrapped

We may wrap regex literals to make them easier to read.

For instance, instead of writing:

/baz/.test("bar");

We write:

(/baz/).test("bar");

Spacing Around the * in yield* Expressions

We usually don’t put a space between yield and * whem we call generator functions within another generator function.

So instead of writing:

function* generator() {
  yield * foo();
}

We write:

function* generator() {
  yield* foo();
}

Conclusion

We should use slice rather than substr or substring .

Also, we should use trimStart and trimEnd so that it’s consistent with padStart or padEnd .

When we encounter types that we don’t expect, we should throw TypeError s instead of generic errors.

Categories
JavaScript Best Practices

JavaScript Best Practices — Async, Generators, Spaces, and Sorting

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

No Assignments that Lead to Race Conditions with await or yield

await is always async and yield can also be async, so if we make assignments in parallel, then it’ll lead to race conditions.

For instance, if we have:

let length = 0;

async function addLength(pageNum) {
  length += await getPageLength(pageNum);
}

Promise.all([addLength(1), addLength(2)]).then(() => {
  console.log(totalLength);
});

Then we don’t know when addLength for each call will finish.

We should run these in sequence like:

async function addLengths() {
  const length1 = await addLength(1);
  const length2 = await addLength(2);
  return length1 + length2;
}

No async Functions Which have no await Expression

We shouldn’t have async functions that have no await expressions.

If there’s no await , this means we aren’t using promises it, so we don’t need the async keyword.

For instance, instead of writing:

async function foo() {
  doWork();
}

We write:

async function foo() {
  await doWork();
}

JSDoc Comments

We can use JSDoc comments to explain the parameters and describe what the function does.

For instance, we can write:

/**
 * Multiplies two numbers together.
 * @param {int} num1 The first number.
 * @param {int} num2 The second number.
 * @returns {int} The product of the two numbers.
 */
function multiply(num1, num2) {
  return num1 * num2;
}

param explain the parameters and returns explains what the returned result is.

The top has the function description.

Use the u flag on RegExp

We should use the u flag in our regex so that UTF-16 character pairs are handled correctly.

For instance, instead of writing:

/^[?]$/.test("?")

We write:

/^[?]$/u.test("?")

The first one returns false , which is wrong.

But the second returns true , which is right. This is because we added the u flag.

No Generator Functions that do not have Yield

A generator function without yield doesn’t need to be a generator.

So instead of writing:

function* foo() {
  return 10;
}

We write:

function* foo() {
  yield 20;
  return 10;
}

Spacing Between Rest and Spread Operators and their Expressions

We don’t need spaces between the rest and spread operations and their expressions.

For instance, instead of writing:

let arr2 = [1, 2, 3];
arr1.push(... arr2);

or:

fn(... args)

We write:

let arr2 = [1, 2, 3];
arr1.push(...arr2);

or:

fn(...args)

Automatic Semicolon Insertion (ASI)

We should put the semicolons ourselves so that we know where each line starts or ends.

For instance, we can write:

let n = 0
const increment = () => {
  return ++n
}

We write:

let n = 0;
const increment = () => {
  return ++n;
}

Spacing Before and After Semicolons

We should have one space after the semicolon if there’s something after it

For instance, instead of writing:

var c = "foo";var e = "bar";

We write:

var c = "d"; var e = "f";

Location of Semicolons

We should put semicolons where it makes sense.

For instance, instead of writing:

foo()
;[1, 2, 3].forEach(bar)

We write:

foo();
[1, 2, 3].forEach(bar);

It’s more conventional and clearer.

Import Sorting

We can sort our imports to make them easier to follow.

For instance, instead of writing:

import b from 'foo.js';
import a from 'bar.js';

We write:

import a from 'bar.js';
import b from 'foo.js';

Sort Object Keys

We can sort object keys alphabetically to make them easier to find.

So instead of writing:

let obj = {
  a: 1,
  c: 3,
  b: 2
};

We write:

let obj = {
  a: 1,
  b: 2,
  c: 3,
};

Sorting Variables

We can sort variables to make them easier to find,

For instance, instead of writing:

var b, a;

We write:

var a, b;

Conclusion

We can sort object properties, variables, imports, etc,. to make them easier to find.

Also, we put the semicolons in conventional places to make them easier to read.

The u flag should be in a regex to make JavaScript search for character pairs properly.

If there’s no await , then we don’t need an async function.

If there’s no yield , then we don’t need a generator function.

Categories
JavaScript Best Practices

JavaScript Best Practices — Ternaries, Promises, Arrays, and Alerts

To make code easy to read and maintain, we should follow some best practices.

In this article, we’ll look at some best practices we should follow to make everyone’s lives easier.

Operands of Ternary Expressions

Ternary expressions can be spread between multiple lines.

For instance, we can write:

const foo = bar < baz ? value1 : value2;

or:

const foo = bar < baz ?
    value1 :
    value2;

They are both suitable as long as they’re consistent.

Constructor Names Should Begin with a Capital Letter

We should begin constructor names with a capital letter.

For instance, we write:

const friend = new Person();

instead of:

const friend = new person();

This is a commonly accepted convention so we should follow it for consistency.

Use Parentheses When Invoking a Constructor with no Arguments

We should add parentheses when invoking a constructor with no arguments.

Even though we can skip it, we should add it so people know we’re invoking it.

For instance, instead of writing:

const person = new Person;

We write:

const person = new Person();

Empty Line After Variable Declarations

We may want to add a line after a group of variables are declared.

For instance, we can write:

let foo;
let bar;

let abc;

We can group variables with an empty line.

Empty Line Before return Statements

We should remove empty line before return statements.

For instance, we write:

function foo() {
  return;
}

instead of:

function foo() {

  return;
}

The extra empty line looks weird and doesn’t help with clarity.

Newline After Each call in a Method Chain

If we’re calling a long chain of methods, we should put them in their own line.

For instance, instead of writing:

$("#p").css("color", "green").slideUp(2000).slideDown(2000);

We write:

$("#p")
  .css("color", "green")
  .slideUp(2620)
  .slideDown(2420);

Reducing horizontal scrolling is good.

Diffs are also easier to read.

Use of Alert

We shouldn’t use alert for debugging.

The console object or debugger can be used for that.

Instead, we can use alert for displaying alerts as it’s intended.

console have many methods to log expression values.

debugger sets a breakpoint in our code.

Array Constructors

There’s one good use for the Array constructor.

We can use it to create empty arrays and fill it with data.

For instance, we can write:

Array(10).fill().map((_, i) => i)

to make an array that has entries from 0 to 9.

We call the Array constructor with 10 to create 10 empty entries.

Then we use map to map the entries to integers.

No async Function as a Promise Executor

async functions already return promises, so we shouldn’t put it in the constructor.

It’s redundant and any errors are lost and won’t cause the Promise constructor to reject.

For instance, we shouldn’t write:

const result = new Promise(async (resolve, reject) => {
  readFile('foo.txt', (err, result) => {
    if (err) {
      reject(err);
    } else {
      resolve(result);
    }
  });
});

Instead, we write:

const result = new Promise((resolve, reject) => {
  readFile('foo.txt', (err, result) => {
    if (err) {
      reject(err);
    } else {
      resolve(result);
    }
  });
});

We only use it with non async functions.

No await Inside Loops

We should map our items to an array of promises and use Promise.all to run them in parallel instead of looping through each entry and waiting for them to resolve.

Waiting is a lot slower and we can run them in parallel since they don’t depend on each other.

For instance, instead of writing:

const foo = async (things) => {
  const results = [];
  for (const thing of things) {
    results.push(await bar(thing));
  }
  return baz(results);
}

We write:

const = async (items) => {
  const results = items.map(thing => toPromise(item));
  return baz(await Promise.all(results));
}

We map things to an array of promises with a function.

Then we can use await with Promise.all to call them all at once.

Conclusion

We should use Promise.all to call independent promises.

Variable declarations can be grouped with lines.

Ternary expressions can be put in one line or multiple lines.

Method chains can be put in multiple lines.

Never put async functions in the Promise constructor.

The Array constructor is good for creating empty arrays.