Categories
JavaScript Best Practices

JavaScript Best Practices — DOM Performance

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.

Too Many Interactions with the Host

We shouldn’t interact too much with any DOM objects.

They render slowly and we should reduce the number of interactions that are done.

Too Many Dependencies

If we have too many dependencies, then we should reduce them.

Over the years, many things are added to the JavaScript standard library.

So we don’t have to use 3rd party dependencies to do the same thing.

For instance, Lodash array methods can be replaced with built-in array methods.

3rd party HTTP clients can be replaced with the Fetch API.

Poor Event Handling

To improve performance of event handling, we should use them properly.

We should remove unnecessary loops and unbind unused event handlers with jQuery unbind or plain JavaScript’s removeEventListener to remove them.

Unorganized Code

To make our lives easier, we should organize our code into coherent chunks.

This way, we can find the code later.

Use HTTP/2

HTTP/2 is the latest version of JavaScript and provides many enhancements over the original version.

We’ll see speed improvements if we switch to HTTP/2 server for hosting our site.

Use Pointer References

We should catch our DOM objects in a variable so that we don’t have to get them again from the DOM if we need it later.

We just take the variable and use that.

For instance, we can write:

const fooEl = document.querySelector('.foo');

to get the element with the class foo .

Trim our HTML

We can reduce the number of items that needed to be loaded by reducing the elements on our page.

The fewer we have, the less we need to load, and the faster our page will be.

Use document.getElementById()

We can use getElementById .

Getting things by ID is faster since there’s only one element with a given ID on a page.

This means our browser doesn’t have to check all the nodes to find all the items.

For example, we can write:

const button = document.getElementById('window-minimize-button');

Use document.querySelector()

If we only want to get one element with the given selector, we can use querySelector to get the DOM node with the given selector.

The browser stops looking after the first node with the given selector is found, which makes the lookup faster.

For example, we can write:

`const button = document.`querySelector`('#window-minimize-button');`

Batch our DOM Changes

We should batch our DOM changes so that we won’t rerender multiple times.

This way, our web page would feel faster for the user.

Buffer our DOM

If we have scrollable divs, we can use a buffer to remove items from the DOM that aren’t currently visible in the viewport.

This will save memory usage and DOM traversal.

There are many libraries to help us with this like React Virtualized for React apps.

Conclusion

We can apply various techniques to speed up DOM manipulation.

These can make a big difference in performance.

Categories
JavaScript Best Practices

JavaScript Best Practices — Readability

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.

Don’t Use Logical Operators as Statements

We shouldn’t write clever but hard to read code.

For example, something like:

a() || b.foo.bar(); m && c() ? d() : l || m.baz[bee](3);

is impossible to read.

And therefore this should be avoided.

Always Declaring Variables at the Top of Their Scope

We can put variables at the top of their scope:

function calculate() {
  let width, height, length;
  // ...
}

This way, they’re easy to see and we can work with them anywhere.

Repeatedly Used Inline Object Literals

If we have object literals that are repeatedly used with some variation, we can put them in a function.

For example, we can write:

function prepareRequest(mode, updateId, value) {
  const req = {
    mode,
    updateId,
    value,
    uid: genUID()
  };
  //...
}

This way, we can call prepareRequest and use it anywhere.

Complex Inline Regex

Complex inline regex is impossible to read.

For example, if we have:

if (/^((\[^<>()[]\.,;:\s@"\]+(\.\[^<>()[]\.,;:\s@"\]+)\*)|(".+"))@(([\[0-9\]{1,3}\.\[0-9\]{1,3}\.\[0-9\]{1,3}\.\[0-9\]{1,3}\])|((\[a-zA-Z\-0-9\]+\.)+\[a-zA-Z\]{2,}))$/.test(str)) {
  // ...
}

We have no idea what it’s checking.

Instead, we can assign it to variable and then use it like:

const emailRegex = /^((\[^<>()[]\.,;:\s@"\]+(\.\[^<>()[]\.,;:\s@"\]+)\*)|(".+"))@(([\[0-9\]{1,3}\.\[0-9\]{1,3}\.\[0-9\]{1,3}\.\[0-9\]{1,3}\])|((\[a-zA-Z\-0-9\]+\.)+\[a-zA-Z\]{2,}))$/

if (emailRegex.test(str)) {
  // ...
}

Now we know the regex is actually used for checking for a valid email address.

Strict Equality Everywhere

Strict equality just helps us with avoiding issues with automatic data type coercion.

The rules for data type conversion is complex with the == or != operators.

So we should instead use the === and !== operators to check for equality and inequality respectively.

For example, we can write:

if (typeof x === 'string' && x === 'abc') {
  // ...
}

to compare their type and value.

Assuming Truthiness Equals Presence

We shouldn’t assume truthiness means something ins present in an object.

To check if a key is in an object, we should use hasOwnProperty or the in operator.

Instead of writing:

if (!cache[key]) {
  cache[key] = value;
}

We should write:

if (!cache.hasOwnProperty(key)) {
  cache[key] = value;
}

or:

if (!(key in cache)) {
  cache[key] = value;
}

The in operator also checks the prototype for a property.

Commenting Nothing or Commenting Every Little Thing

No comments and commenting on everything are both bad.

Sometimes there are things that need an explanation that isn’t in the code.

However, it’s also bad to make comments on things that are already covered by the code.

Write Something Reliable without Unit Tests

It’s just hard to write something reliable without unit tests.

They definitely give us peace of mind when we’re changing code.

Using Long Names and Being Overly Specific

We shouldn’t have names that are too long and are overly specific.

Names like:

function generateNewtIDForANewRequestCacheItem() {...}
function deleteRequestCacheItemsThatDoNotEqual(value) {...}

They are definitely too long and too specific.

We should definitely find ways to reduce the length of them.

They have lots of extra information we don’t need.

Conclusion

We should take note of some practices to make our code easier to read.

We may go overboard with some good practices as well.

Categories
JavaScript Best Practices

JavaScript Best Practices — Arrays and Exceptions

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.

Avoid Using for-in Loop for Arrays

We should avoid using for-in loop for arrays.

For instance, instead of writing:

let sum = 0;
for (let i in arrayNumbers) {
  sum += arrayNumbers[i];
}

We can use a for loop or a for-of loop:

let sum = 0;
for (let a of arrayNumbers) {
  sum += a;
}

or:

let sum = 0;
for (let i = 0, len = arrayNumbers.length; i < len; i++) {
  sum += arrayNumbers[i];
}

A for-in loop is used for iterating through regular objects rather than arrays.

Pass Functions, not Strings, to setTimeout() and setInterval()

We should always pass functions to setTimeout and setInterval .

For example, instead of writing:

setInterval('doSomething()', 1000);
setTimeout('doSomething()', 5000);

We write:

setInterval(doSomething, 1000);
setTimeout(doSomething, 5000);

Use a switch/case Statement Instead of a Series of if/else

switch and case are shorter than a series of if and else .

For example, we can write;

switch (val) {
  case 1:
    //...
    break;
  case 2:
    //...
    break;
  case 3:
    //...
    break;
  case 4:
    //...
    break;
  default:
    //...
}

This is better organized than:

if (val === 1) {
  //...
}
else if (val === 2) {
  //...
}
else if (val === 3) {
  //...
}
else if (val === 4) {
  //...
}
else {
  //...
}

If we have more than 10 cases, we should avoid a series of if and else s.

Use switch/case Statement with Numeric Ranges

We can use switch and case with numeric ranges.

To do that, we can write:

switch (true) {
  case isNaN(age):
    category = "not an age";
    break;
  case (age >= 50):
    category = "old";
    break;
  case (age <= 20):
    category = "kid";
    break;
  default:
    category = "young";
    break;
};

Instead of passing a variable switch , we pass in true .

Also, the case has expressions instead of a value.

Create an Object Whose Prototype is a Given Object

We can create an object whose prototype is the gin object with he Object.create method.

For example, we can write:

const obj = Object.create(proto);

where proto is the prototype object for obj .

An HTML Escaper Function

We can escape HTML with some regex.

For example, we can write:

function escapeHTML(text) {
  const replacements = {
    "<": "&lt;",
    ">": "&gt;",
    "&": "&amp;",
    """: "&quot"
  };
  return text.replace(/[<>&"]/g, (character) => {
    return replacements[character];
  });
}

We search for the brackets, ampersands, and quotes with the regex and then we get the replacement character from the replacements object.

Avoid Using try-catch-finally Inside a Loop

try-catch-finally creates a new variable in the current scope at runtime each time the catch clause is run.

So instead of writing:

let arr = ['foo', 'bar'], i;
for (i = 0, len = arr.length; i < len; i++) {
  try {
    // ...
  } catch (e) {
    // ...
  }
}

We write:

let arr = ['foo', 'bar'],  i;
try {
  for (i = 0, len = arr.length; i < len; i++) {
    // ...
  }
} catch (e) {
  // ...
}

Now the catch clause can only be run once with the 2nd example.

So variables in there can only be created once.

Conclusion

We shouldn’t use for-in loops with arrays.

Also, we can create objects with a prototype with Object.create .

There’re also many things to watch out for with arrays.

Categories
JavaScript Best Practices

Robust JavaScript Best Practices

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.

Consider Using try/catch When Using JSON.parse

There’s always a chance that we encounter invalid JSON strings.

So to prevent that from crashing our app when we parse JSON, we should wrap try/catch around our JSON.parse call.

For example, we can write:

try {
  JSON.parse(str);
}
catch(ex) {
  console.error(ex);
}

This way, we won’t crash our app when we try to parse invalid JSON.

Using Classes

Classes are just syntactic sugar above prototypes.

However, it looks more familiar to most people.

And it’s easier to understand than prototypes.

For example, instead of writing:

function Dog(name) {
  this.name = name;
}

Dog.prototype.speak = function() {
  console.log(this.name);
}

We write:

class Dog {
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(this.name);
  }
}

They do the same thing, but the 2nd example makes more sense.

We can definitely consider them when we create constructors.

Using Arrow Functions

Arrow functions are useful because they don’t bind to their own this and it’s shorter.

This means it’s handy for arrow functions.

If we don’t need its own this inside the function, we should use arrow functions.

For instance, we can write:

const foo = (msg) => {
  console.log(msg);
};

or:

const init = (msg) => console.log(msg);

to write them.

This makes dealing with JavaScript functions much easier.

Concatenating Strings and Templating

Tenplate strings are great alternatives to concatenation.

We can interpolate expressions in a string without having complex concatenation expressions.

For instance, instead of writing:

const msg = 'I said, "hello' + firstName + ' ' + lastName + '" to the world.';

We can write:

const msg = `I said, "hello ${firstName} ${lastName}" to the world.`;

It’s much cleaner and it’s also a bit shorter.

Destructuring Arrays and Objects

The destructuring syntax it’s another great JavaSctipt feature.

Before the destructuring syntax existed, destructuring array entries to variables is a lot harder.

We’ve to write:

const arr = [1, 2, 3, 4];
const a = arr[0];
const b = arr[1];
const c = arr[2];
const d = arr[3];

to assign each entry to its own variable.

With the destructuring syntax, it’s much shorter:

const [a, b, c, d] = [1, 2, 3, 4];

With objects, instead of writing:

const obj = { foo: 1, bar: 2, baz: 3 };
const foo = obj.foo;
const bar = obj.bar;
const baz = obj.baz;

We write:

const { foo, bar, baz } = { foo: 1, bar: 2, baz: 3 };

It’s much simpler.

Componentizing Our Code

We can organize our code into modules.

To do that, we just create JavaSctipt files that has export statements.

For example, we can write:

bar.js

export const foo = 66;

Then we can import it by writing:

import { foo } from 'bar';

It’s much easier than using scripts with a bunch of global variables, which can easily be overwritten.

It’s a basic building block of JavaScript apps.

Conclusion

We can use modern JavaScipt constructs like the class syntax, modules, and destructuring to make our lives easier.

Categories
JavaScript Best Practices

JavaScript Best Practices — Performance Improvements

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.

Excess Optimizations

We shouldn’t optimize our code too much.

If there aren’t obvious performance issues then we can leave it.

And if it makes our code harder to work it, we shouldn’t make the change.

Causing Excessive Document Reflows

DOM modification is slow since our browser has to rebuild the DOM tree and recalculate all the dimensions to see how they fit together.

It’s more of a problem if elements have the default static positioning or relative.

So we should keep DOM modifications to a minimum.

To reduce the operations, we should batch our DOM manipulation.

For example, we can write:

let div = $('<div></div>');

$('body').append(div);

for (let i = 0; i < 10000; i++) {
  $('div').append(
    '<p>foo</p>'
  );
}

We append to p elements to the div 1000 times.

This is obviously going to be very slow.

Instead, we should only call append on the body one time:

let div = $('<div></div>');

for (let i = 0; i < 10000; i++) {
  $('div').append(
    '<p>foo</p>'
  );
}

$('body').append(div);

In the code above, we call append to append the p elements to the div, which isn’t in the DOM yet.

So we won’t be changing the DOM 10000 times.

The only change is the last line where we append the div to the body.

Going Overboard with File Concatenation

We shouldn’t concatenate everything into one big file.

One big request may be slower than multiple smaller requests.

This is because once a request is made for the resource, it’ll be cached until it’s updated again.

Having multiple smaller files can take advantage of that.

If we have one big file, then we can’t take advantage of the caching.

The best way to combine the code is to have one common file that’s used everywhere.

And then we have page-specific files that are only used on the page requested.

Really Long Function Chains

Function chains can be handy.

But we should be aware that we may have null or undefined returned in the middle of the chain.

For example, if we have:

$('.el').click(handleClick).parents('.container')[0].slideDown();

then any of the methods could return those values and crashing our app.

Flash of Unstyled Content (FOUC) Because of Late-Loading JavaScript

We may see unsettled content for a brief period before showing the right design.

The best way to improve the situation is to reduce the amount of DOM manipulation on the initial load.

Also, we should specify the height and width of the pages in our HTML instead of JavaScript so that we don’t have to wait for JavaScript to load to set them.

Another way to deal with it is to mask the problem with CSS or JavaScript animations.

We can also block rendering until all content on the page has been loaded.

The offending JavaScript can be inlined and put beneath the affected DOM element.

This way, the code loads immediately when the element is loaded.

However, this is hard to maintain and should be used sparingly.

We can write:

<div class="fouc-element">
  <script>
    // ...
  </script>
  ...
</div>

to do that.

Conclusion

We can improve performance with various optimizations.