Categories
JavaScript Best Practices

JavaScript Best Practices — Reducing Headaches

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.

Use === to Test Equality

=== is the best operator for testing equality.

The type and value are both checked to test for equality.

== converts the value according to its own rules before doing the comparison.

For example, we can write:

1 === '1'

returns false .

But:

1 == '1'

returns true because of the automatic data type conversion.

Likewise, we can use !== to check for inequality.

For example, we can write:

1 !== '1'

which returns true .

And:

1 != '1'

returns false for the same reason.

It’s easier to work with !== and === .

Include All Semicolons

We should put semicolons at the end of statements to avoid the JavaScript interpreter inserting them automatically for us.

This reduces the chance of errors since we’re in control.

Use ESLint

ESLint is the most popular linter for JavaScript.

We should use it to lint our code to catch errors and find formatting issues with our code.

It’ll pick up bad code so that we can fix them.

Also, it comes with many plugins for TypeScript and various style guides.

Use a Namespace

We should namespace our variables to avoid name collisions.

Instead of writing:

let price = 5;

We write:

let namespace = {};  
namespace.price = 5;

This way, we segregate the variable by putting the value in the namespace.

Avoid Using Eval

eval is definitely bad since it runs code from a string.

This is a security issue and prevents any kind of optimization from being done.

Debugging is also very hard since the code is in a string.

Use Decimals Cautiously

We should be careful with decimal operations.

For instance if we have:

0.1 + 0.2

it won’t return 0.3 as we expect.

Instead, we get:

0.30000000000000004

This is because floating-point numbers are represented with binary numbers.

The conversions between the 2 number systems mean that floating-point arithmetic won’t be exact.

We should, therefore, round them to the number of decimal places that we want to return.

Start Blocks on the Same Line

Blocks should start on the same line as the keyword.

For example, instead of writing:

if(environment === 'testing')   
{  
   console.log('testing');  
}   
else   
{  
   console.log('production');  
}

We should write:

if (environment === 'testing') {  
  console.log('testing');  
} else {  
  console.log('production');  
}

We can avoid errors in some cases avoid statements like:

return  
{  
   age: 15,  
   name: 'james'  
}

The object isn’t returned since it’s considered to be separate from the return statement.

Instead, we write:

return {  
  age: 15,  
  name: 'james'  
}

so we return the object.

Use Explicit Blocks

We should delimit blocks explicitly with curly braces.

This way, we won’t have any confusion on where a block starts or ends.

For example, instead of writing:

if (i > 3)  
  doWork();

We write:

if (i > 3) {  
  doWork();  
}

Now we won’t have any confusion with the blocks.

Conclusion

We can follow some basic best practices to reduce headaches.

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.