Categories
JavaScript Best Practices

JavaScript Best Practices — Performance and Tests

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.

The RAIL Model

The RAIL model is created by Google to let us think about performance from the user’s perspective.

RAIL stands for Response, Animation, Idle, and Load.

Response is when the page first responds to user input.

Animation is when the page moves.

Idle is how long the page stays idle.

Load is long the page takes to load.

We should make our app respond to interaction as quickly as possible.

Find Memory Leaks

We should check for memory leaks with various tools.

One good tool is the Chrome dev tools, which has the Performance to show what’s using memory in our app.

We can also check for memory usage of a tab with the Chrome Task Manager.

Timeline recordings, visualize memory usage over time.

Heap snapshots help us identify detached DOM trees.

Allocation timeline recordings let us find out when a new memory is being allocated in our JS heap.

We know that we have memory leaks if the memory usage increases over time until all available memory is used.

Use Web Workers for Intensive Tasks

If we have intensive tasks on our app, we can use web workers to run them in the background.

It works by sending input to the worker and then when the worker is done, the results is sent back to the main app.

They work on a different thread so they won’t hold up the main thread.

Beware of Computational Complexity

We should be aware of computational complexity of the algorithms we’re using.

Things like nested loops and other slow code should be avoided.

Simplify

If we can write something in a simpler way, then we should do so.

Avoid Recursive Calls

If there’s an iterative way to do something instead of recursion, then we should use iteration.

It’s easier to read and the performance is good.

Regex

We can use regex to search for patterns in strings.

So we can use them to extract information.

For instance,e we can write:

str.replace(/\s+/g, '');

to trim whitespace.

We can also check email address format with:

function validateEmail(email) {
  const re = /\S+@\S+\.\S+/;
  return re.test(email);
}

Use Search Arrays

We can search arrays with the find function.

For example, we can write:

let array = [1, 2, 3, 4, 5];
let found = array.find((element) => {
  return element > 2;
});

to find elements in an array.

We can also use this to avoid switch statements by checking for something in an array instead of using each array entry as a case .

Use Google Lighthouse

Lighthouse is a good tool for checking the performance of our browser app.

It checks for performance, accessibility, best practices, and SEO.

Lighthouse runs in Chrome dev tools in the Lighthouse tabs and it’s also available as a Node package so we can run the checks automatically.

Google PageSpeed

Google PageSpeed lets us understand a website’s performance optimizations and find areas that we can improve on.

It’s used to identify issues with compliance with Google’s web performance best practices.

JavaScript Navigation Timing API

The JavaScript navigation timing API lets us measure the performance of our website.

We can use it by writing:

const perfData = window.performance.timing;
const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart;

We just subtract the loadEventEnd and navigationStarty timestamps to get the loading time.

We can also calculate request response time with:

const connectTime = perfData.responseEnd - perfData.requestStart;

and page rendering time with:

const renderTime = perfData.domComplete - perfData.domLoading;

This describes the level 1 API, which is deprecated, but level 2 is coming.

We can use this until level 2 is available.

Test Our Code

We should test our code with unit tests, integration tests, and user interface tests.

Unit tests test small parts of the code like functions.

Integration tests test bigger parts of the code like multiple functions or classes.

UI tests test from the perspective of the user but in an automated fashion.

Clicks and visual checks are done.

Conclusion

We should make sure the performance of our app is good.

Also, we should test our code in various ways.

Categories
JavaScript Best Practices

JavaScript Best Practices — Front End

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.

Event Delegation

We can listen to the events from all the child elements with one event listener attached to the parent.

For example, we can write:

document.addEventListener('click', (event) => {
  const {
    currentTarget
  } = event;
  let {
    target
  } = event;

  if (currentTarget && target) {
    if (target.nodeName !== 'LI') {
      while (currentTarget.contains(target)) {
        target = target.parentNode;
      }
    }
  }
});

We can check which element emitted the event in the event listener.

All the data is in the event object.

currentTarget has the element that now propagates the event.

target has the original element that emits the event.

Debounce, Throttle, and requestAnimationFrame

Debouncing a function prevents it from being called again until a given amount of time has passed.

We can do this easily with Lodash’s debounce method.

Throttling only causes the function to be called a max number of times.

Lodash also has a throttle method.

requestAnimationFrame lets us throttle to 60fps when we run animation.

Client-Side Data

We can get data on the client-side with the Fetch API.

It’s a promise-based API that lets us make HTTP requests on client-side apps.

If it’s not available, we can add a polyfill for it.

When to Use a Client-side Data Request Library

We can use a client-side request library to make cancelable requests, add timeouts, or get request progress.

They’re all made easier with 3rd party libraries like Axios.

They also have transformers, interceptors, and XSRF protection that can be applied to all requests.

We can also make helper functions that add error handling, cookie, CORS, etc.

Concatenating Requests

GraphQL is an alternative way to make HTTP requests which don’t send JSON.

Instead, it has its own query language to make requests via HTTP.

It’s an alternative worth considering since it has type checks and lets us selectively get data by field.

This means the calls ate more targeted and it’s more efficient.

Unit and Integration Testing

Automated testing makes our lives easier.

We can write unit tests to test some under the surface functionality.

Integration tests test more functionality.

However, visual checks still need to be done manually.

Libraries

We need to update our libraries often to keep them up to date.

They often have security fixes and new features to make our lives easier.

Also, utility libraries make our lives easier. They have many helper functions to help us.

For instance, Lodash and Underscore have many things like clone , each , chunk and more.

Frameworks

Frameworks help us with developing faster.

We can use popular ones like React and Vue which have lots of libraries that work with them to help build apps faster.

Cookies

We can use local storage with a unique identifier in addition to cookies to make sure users that has the cookie is actually the user that should have it.

Conclusion

There’re many things we may have not considered.

We can secure cookies with more tracking.

Many libraries make our lives easier.

Categories
JavaScript Best Practices

JavaScript Best Practices — Event Listeners, Arrays, and null

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.

Don’t Use of the null Literal

Using undefined for everything is better than using null and undefined .

So we just stick with undefined for everything.

For instance, instead of writing:

let foo = null;

or:

if (bar == null) {}

We write:

let foo;

or:

if (foo === null) {}

No process.exit()

We don’t want to use process.exit to exit code.

It exists the program abruptly without giving it a chance to clean up.

Instead of writing:

process.exit(0);

We write:

process.on('SIGINT', () => {
  console.log('Got SIGINT');
  process.exit(1);
});

Use Array#reduce() and Array#reduceRight()

reduce and reduceRight make code hard to read.

They can replaced with map , filter or the for-of loop.

For instance, instead of writing:

array.reduce(reducer, initialValue);

We write:

let result = initialValue;

for (const element of array) {
  result += element;
}

No Unreadable Array Destructuring

If we write an array destructuring code, then we should make them readable.

For instance, instead of writing:

const [, , foo] = parts;

We write:

const [foo] = parts;

No Unused Object Properties

If we have object properties that aren’t used, then we may want to remove them.

For instance, instead of writing:

const obj = {
  used: 1,
  unused: 2
};

console.log(obj.used);

We write:

const obj = {
  used: 1
};

console.log(obj.used);

No Useless undefined

We shouldn’t use undefined in places where they aren’t usefil.

For instance, instead of writing:

let foo = undefined;

const {
  foo = undefined
} = bar;

const baz = () => undefined;

function foo() {
  return undefined;
}

function* foo() {
  yield undefined;
}

function foo(bar = undefined) {}

function foo({
  bar = undefined
}) {}

foo(undefined);

We write:

let foo;

const {
  foo
} = bar;

const baz = () => {};

function foo() {

}

function* foo() {

}

function foo(bar) {}

function foo({
  bar
}) {}

foo();

We don’t need a function that return undefined .

And should return or yield undefined since they’re done automatically.

No Number Literals with Zero Fractions or Dangling Dots

We don’t want to add dangling dots or zero fractions since they’re redundant.

For instance, instead of writing:

const foo = 1.0;
const foo = -1.0;
const foo = 1.;

We write:

const foo = 1;
const foo = -1;
const foo = 1;

Enforce Proper Case for Numeric Literals

We should have proper casing for numeric literals.

For instance, instead of writing:

const foo = 0XFF;
const foo = 0B10;
const foo = 0O76;

We write:

const foo = 0xFF;
const foo = 0b10;
const foo = 0o76;

Use .addEventListener() and .removeEventListener() Over on-Functions

Instead of using on functions for adding event listeners, we can use addEventListener and removeListener .

With addEventListener , we can add and remove the listener.

We can also specify capture mode rather than bubbling.

Also, we can register an unlimited number of handlers.

It also separates logic from the document structure.

And confusion with correct event names is eliminated.

on- properties don’t respond to mistakes properly.

We can assign anything to it and get no errors, which isn’t good.

Therefore, instead of writing:

foo.onclick = () => {};

We should write:

foo.addEventListener('click', onClick);

and:

foo.removeEventListener('click', onClick);

Use .dataset on DOM Elements over .setAttribute

Instead of using the setAttribute property to set custom attributes, we can use the dataset property instead.

It’s harder to make mistakes with it.

For instance, instead of writing:

element.setAttribute('data-foo', 'bar');

We write:

element.dataset.foo = 'bar';

Use KeyboardEvent#key over KeyboardEvent#keyCode

The keyCode property is deprecated. So we should use the key property to check for key codes.

For instance, instead of writing:

window.addEventListener('keydown', event => {
  console.log(event.keyCode);
});

We write:

window.addEventListener('keydown', event => {
  console.log(event.key);
});

Conclusion

We should use undefined instead of null .

Also, we should use addEventListener and removeEventListener instead of using on functions.

Loops and other array methods are better than reduce since the code is more readable.

Categories
JavaScript Best Practices

JavaScript Best Practices — Speed and Spread

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.

The Spread Operator and Arrays

The spread operator can do many things.

We can use them to merge arrays.

For instance, we can write:

const a = [1, 2, 3];
const b = [4, 5, 6];
const c = [...a, ...b];

c has all the entries of a and b .

We can also spread an array into a function as its arguments.

For example, we can write:

const sum = (x, y, z) => x + y + z;
const nums = [1, 2, 3];
const total = sum(...nums);

We use the spread operator spread the nums entries as arguments.

Defer the Loading of Scripts

We can defer the loading of scripts with the defer attribute.

This way, they’ll load asynchronously in the order they’re listed.

For example, we can write:

<script defer src='script.js'></script>

We can also put script tags at the end of a file so that they load last.

Save Bytes by Using Minification

Before we deploy our app to production, we should minify them so that we reduce the package size.

If we use frameworks, this should be done automatically by the CLI program for those frameworks.

If we don’t we can use Parcel or Webpack to do the modification for us.

Avoid Unwanted Loops

We should avoid nested loops to keep our code linear.

Also, we don’t want our loop to go through many objects since it’ll be slow.

Prefer Map Over for Loop

If we’re mapping array entries from one thing to another, we can use the array instance’s map method.

This way, the original array doesn’t change.

The scope is isolated since the mapping is done with a callback.

It’s also shorter than a loop.

And we can compose with other operations like loops and other array methods.

For instance, we can write:

let strings = ['1', '2', '3', '4', '5'];
const total = strings
  .map(str => +str)
  .filter(num => num % 2 === 1)
  .reduce((sum, number) => sum + number);

to convert the strings entries to numbers with map .

Then we call filter to return the odd numbers from the mapped array.

And finally, we call reduce to get the total of the odd numbers.

With loops, this is much longer.

We’ve to write:

let strings = ['1', '2', '3', '4', '5'];
let nums = [];
let oddNums = [];
let total = 0;

for (const s of strings) {
  nums.push(+s);
}

for (const n of nums) {
  if (n % 2 === 1) {
    oddNums.push(n);
  }
}

for (const o of oddNums) {
  total += o;
}

We have 3 loops to do the mapping, filtering, and adding respectively.

It’s just much shorter to use array instance methods.

Use Proper Event Handlers

We can use event handlers by using event delegation to check for what DOM element triggered the event.

Also, we should remove them when we’re done with the with removeEventListener .

To do event delegation, we can write:

document.addEventListener('click', (e) => {
  if (e.srcElement.tagName.toLowerCase() === 'div') {
    //...
  }
})

to check for clicks for divs.

To call removeEventListener , we can write:

target.removeEventListener('click', onClick);

where onClick is an event listener function.

Remove Unused JavaScript

If we have unused code, then we should remove them.

This way, no one will be confused about them.

Avoid Using Too Much Memory

Every time our code uses more memory, the garbage collector will be run when to clean up unused resources later.

If garbage collection runs frequently, then our app runs slowly.

So we should avoid reserving memory in the first place.

Cache in the Browser

We can cache things with service workers.

We can cache things with the Cache API.

To add caching, we can write:

const CACHE_VERSION = 1;
``const CURRENT_CACHES = {
  font: `font-cache-v${CACHE_VERSION}`;
};``

self.addEventListener('activate', (event) => {
  let expectedCacheNamesSet = new Set(Object.values(CURRENT_CACHES));
  event.waitUntil(
    caches.keys().then((cacheNames) => {
      return Promise.all(
        cacheNames.map((cacheName) => {
          if (!expectedCacheNamesSet.has(cacheName)) {
            return caches.delete(cacheName);
          }
        })
      );
    })
  );
});

We delete data from the cache by looping through the keys and checking for items that aren’t named in CURRENT_CACHES .

Conclusion

We can use modern features like service works and spread operator to help us make our app better.

Also, we should keep performance in mind with a few things to look for.

Categories
JavaScript Best Practices

JavaScript Best Practices — Things We Need to Learn

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.

Learn About Scopes

We should learn about scopes of variables and functions.

Variable scopes should be easy now with let and const variables.

They’re only accessible within the block so there’s no ambiguity.

Also, we should know what’s accessible from modules.

Only things that are exported can be imported.

The tricky ones are var variables and functions declares with the function keyword.

var is function scoped so it’s available throughout a function.

function declarations can be hoisted.

So functions like:

function foo(){
  //...
}

can be called before they’re defined in the code.

Use the Fastest Way to Loop Through Variables

The fastest way to loop through variables if the good old for loop.

So we write:

let arrayLength = array.length;
for(let i = 0 ; i < arrayLength; i++) {
   let val = array[i];
}

to loop through items.

However, for convenience, the for-of loop and forEach are also good ways to loop through items without sacrificing performance too much.

If we need to map data, we use map .

If we need to return a new array that has data that matches a given condition, we use filter .

There’s also the while loop to loop through data.

Don’t Do Too Much Nesting

Too much nesting is hard to read.

This also means they’re hard to change and debug.

For example, we shouldn’t have things like:

firstFunction(args, () => {
  secondFunction(args, () => {
    thirdFunction(args, () => {
      //...
    });
  });
});

The deeper the nesting, the harder it is to work with, so we should avoid something like this.

Beware of the DOM

If we’re creating client-side JavaScript apps, then we need to manipulate the DOM.

It’s a good idea to know how to add, edit, and remove elements from the DOM.

There’re also many kinds of nodes like text and comment nodes that we’ve to be aware of in addition to element nodes.

Event propagation is also something to think about.

Use the Best Libraries and Frameworks

The top client-side JavaScript frameworks are React with some additional libraries, Angular, and Vue.

React with React Router can be considered a framework.

There’s also Svelte, which is up and coming.

The concepts are transferable between them so if we learn one, we can learn the other,

They all have a component-based architecture, so they’re similar.

Also, there are many libraries built for each of them.

Have Efficient Configuration and Translation

We should put all the configuration and translations in one location so that we can use them everywhere.

This way, we only have to change them in one place if we need to change it.

Reduce Access to Variables and Properties

If we don’t need to access variables, then we should avoid it.

For instance, with a for loop, we can cache the array length to a variable outside the for loop.

Instead of writing:

let names = ['mary', 'james', 'may'];
for (let i = 0; i < names.length; i++) {
  greet(names[i]);
}

We write:

let names = ['mary', 'james', 'may'];
let length = names.length;
for (let i = 0; i < length; i++) {
  greet(names[i]);
}

Don’t Trust Any Data

We shouldn’t trust any data from anywhere.

Therefore, we should validate any input values and check for data from other sources like APIs.

This way, we only work with exactly what we need and avoid any security issues with malicious data.

Use Arrow Functions

Arrow functions are great.

It can be used if we aren’t creating a constructor function.

If we don’t need it to bind to its own this , then we can also use it.

For instance, we can write:

const foo = () => console.log('hello');

It’s shorter and less confusing because it doesn’t bind to its own this .

Use Template Literals

Template literals are much better than concatenation since it’s much easier to read.

And we can put any expressions in them.

For example, instead of writing:

'hello '  + name;

We write:

`hello ${name}`;

Template literals are always delimited by backticks instead of single or double-quotes.

Conclusion

We should use recent features of JavaScript like template literals and arrow functions.

Also, we got to learn about scopes and this .