JavaScript Patterns [Ch.2 | Part 2: Essentials]

JavaScript Patterns [Ch.2 | Part 2: Essentials]

So in the previous article, we talked about part 1 of chapter 2 of Essentials which discussed essential best practices, and habits for writing high-quality JavaScript code in detail, also discussed minimizing globals, Environment global object, The Problem with Globals, How to be a Good neighbor to scripts and more.

In this article, we will continue discussing Chapter 2 titled Essentials. In which we discuss essential best practices and habits for writing high-quality JavaScript code, such as avoiding globals, preaching length in loops, following coding conventions, and more.

This part will focus on multiple concepts like avoiding preaching length in loops, using for-in loops, best practices for using it, multiple micro-optimizations in loops, Implied Typecasting, and more.

🚴‍♀️ For Loops

🚶‍♂️ The Usual Pattern

In for loops you iterate over arrays or array-like objects such as arguments and HTMLCollection objects. The usual for loop pattern looks like the following:

// sub-optimal loop
for (let i = 0; i < myarray.length; i++) {
 // do something with myarray[i]
}

🤦‍♂️ Problems with The Usual Pattern

A problem with this pattern is that the length of the array is accessed on every loop iteration. This can slow down your code, especially when myarray is not an array but an HTMLCollection object.

  • document.getElementsByName()
  • document.getElementsByClassName()
  • document.getElementsByTagName()

🌲 HTMLCollections Operations

The collections are live queries against the underlying document (the HTML page). This means that every time you access any collection’s length, you’re querying the live DOM.

dom.png

💾 Cache the length of the array

DOM operations are expensive in general, That’s why a better pattern for for loops is to cache the length of the array (or collection) you’re iterating over ( retrieve the value of length only once and use it during the whole loop), as shown in the following example:

for (let i = 0, max = myarray.length; i < max; i++) {
 // do something with myarray[i]
}

Caching the length when iterating over HTMLCollections is faster across all browsers, anywhere between two times faster (Safari 3) and 190 times (IE7) (For more details, see High Performance JavaScript by Nicholas Zakas

📝 NOTE:

Sometimes, you explicitly intend to modify the collection in the loop (for example, by adding more DOM elements), you’d probably like the length to be updated and not constant or cached.

💍 Single var Pattern

Following the single var pattern in caching the length of an array, makes it a little harder to copy and paste whole loops while refactoring code

For example, if you’re copying the loop from one function to another, you have to make sure you also carry over i and max into the new function (and probably delete them from the original function if they are no longer needed there).

function looper() {
 let i = 0,
 max,
 myarray = [];
 // ...

 for (i = 0, max = myarray.length; i < max; i++) {
 // do something with myarray[i]
 }
}

🔬 Micro-Optimizations

Another micro-optimization to the loop is to substitute i++ which promotes “excessive trickiness.” with either one of these expressions:

  • i += 1
  • i = i + 1

Two variations of the for pattern introduce some micro-optimizations because they:

  • Use one less variable (no max)
  • Count down to 0, which is usually faster because it’s more efficient to compare to 0 than to the length of the array or to anything other than 0

The first modified pattern is:

let i, myarray = [];

for (i = myarray.length; i--;) {
 // do something with myarray[i]
}

The second uses a while loop:

let myarray = [],
i = myarray.length;

while (i--) {
 // do something with myarray[i]
}

📝 NOTE:

These are micro-optimizations and will only be noticed in performance-critical operations.

❄ For-In Loops

for-in loops used to iterate over non-array objects also called enumeration

Technically, you can also use for-in to loop over arrays (because in JavaScript arrays are objects), but it’s not recommended for multiple reasons:

  • It may lead to logical errors if the array object has already been augmented with custom functionality
  • Additionally, the order (the sequence) of listing the properties is not guaranteed in a for-in

🦺 hasOwnProperty()

It’s important to use the method hasOwnProperty() when iterating over object properties to filter out properties that come down the prototype chain

Consider the following example:

// the object
let man = {
 hands: 2,
 legs: 2,
 heads: 1
};

// somewhere else in the code
// a method was added to all objects
if (typeof Object.prototype.clone === "undefined") {
 Object.prototype.clone = function () {};
}

In this example we have an object called man, Somewhere before or after man was defined, the Object prototype was augmented with a useful method called clone().

The prototype chain is live, which means all objects automatically get access to the new method. So in order to avoid having the clone() method show up when enumerating man, you need to call hasOwnProperty() to filter out the prototype properties.

// 1.
// for-in loop
for (let i in man) {
 if (man.hasOwnProperty(i)) { // filter
 console.log(i, ":", man[i]);
 }
}
/*
result in the console
hands : 2
legs : 2
heads : 1
*/
// 2.
// antipattern:
// for-in loop without checking hasOwnProperty()
for (let i in man) {
 console.log(i, ":", man[i]);
}
/*
result in the console
hands : 2
legs : 2
heads : 1
clone: function()
*/

🚧 Avoiding Implied Typecasting

JavaScript implicitly typecasts variables when you compare them. That’s why comparisons such as false == 0 or "" == 0 return true.

To avoid confusion caused by the implied typecasting, always use the === and !== operators that check both the values and the type of the expressions you compare:

let zero = 0;
if (zero === false) {
 // not executing because zero is 0, not false
}

// antipattern
if (zero == false) {
 // this block is executed...
}

There’s another school says that it’s redundant to use === when == is sufficient. For example, when you use typeof you know it returns a string, so there’s no reason to use strict equality

📝 NOTE:

JSLint requires strict equality; it does make the code look consistent and reduces the mental effort when reading code. (“Is this == intentional or an omission?”)

I hope you enjoyed reading this article. See you in the next part 🚀