Categories
JavaScript Best Practices

JavaScript Best Practice — New Constructs and Functions

Like any other programming language, JavaScript has its own list of best practices to make programs easier to read and maintain. There are also lots of tricky parts to JavaScript. We can follow some best practices easily to make our JavaScript code easy to read.

In this article, we’ll look at replacing IIFE with modules and blocks, replacing traditional functions with class methods and arrow functions, removing language attribute from script tags, writing pure functions, and avoiding a long list of arguments.

Replacing IIFE with Modules and Blocks

IIFE stands for Immediately Invoked Function Expression. It’s a construct where we define a function and then call it immediately.

It’s a popular way to isolate data to prevent them from being accessed from, the outside. Also, it hides them from the global scope.

This is handy before ES6 because there were no standards for modules and there’s also no easier way to hide things from the global scope.

However, with ES6, modules are introduced as a new feature. This means that we can replace something like:

(function() {
  let x = 1;
})()

With a module that has:

let x = 1;

If we don’t want it to be available outside the module, we just don’t export it.

With modules, there’s also no need for namespacing with IIFEs since we can put modules in different folders to separate them from each other.

Also, another new feature of ES6 is blocks. Now we can define blocks of code segregated from the outside scope by using curly braces as follows:

{
  let x = 1;
  console.log(x);
}

With block-scope keywords like let or const for variable or constant declaration, we don’t have to worry about things that we don’t want to be accessed outside from being accessed.

Now we can define arbitrary blocks without if statements, loops, or IIFEs.

Replace Traditional Functions with Arrow Functions and Class Methods

Again, with ES6, we have the class syntax for constructor functions. With that, we don’t need the function keyword to create constructor functions.

It makes inheritance much easier and there’s no confusion about this .

In addition, we have arrow functions that don’t change the value of this inside the function.

The only reason left to use the function keyword is for generator functions, which are declared with the function* keyword.

For example, we can create a simple generator function by writing the following:

const generator = function*() {
  yield 1;
  yield 2;
}

Note that generator functions can only return generators so we can’t return anything else from it.

From the Language Attribute From Script Tags

The language attribute no longer has to be included with script tags.

For example, instead of writing:

<script src="[https://code.jquery.com/jquery-2.2.4.min.js](https://code.jquery.com/jquery-2.2.4.min.js)" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous" language='text/javascript'></script>

We write:

<script src="[https://code.jquery.com/jquery-2.2.4.min.js](https://code.jquery.com/jquery-2.2.4.min.js)" integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>

This is because JavaScript is the language left that runs in browsers.

Photo by Ambitious Creative Co. – Rick Barrett on Unsplash

Write Pure Functions

We should write functions as pure functions. That is functions that always gives the same output given that we pass in the same input.

This is important because it makes testing easy. Also, we know what it’ll do exactly, reducing the chance of bugs.

Pure functions are easy to read and understand. The flow is determinate and simple. Therefore, it’s predictable.

An example of a pure function would be the following:

const add = (a, b) => a + b;

The add function always givens the same output if we give it the same inputs since it just computes the results from the parameters. It doesn’t depend on anything else.

Therefore, the logic flow of it is very predictable.

An example of a function that’s not a pure function would be one that returns different output even if the same inputs are given. For example, if we have a function to get the year number that is x years before this year:

const getYearBefore = (x) => new Date().getFullYear() - x;

This wouldn’t be a pure function because new Date() changes according to the current date. So the result of getYearBefore depends on the current date’s year given the same input.

To make it a pure function we can instead write:

const getYearBefore = (date, x) => date.getFullYear() - x;

Since the date is now passed into the function, we get the same results given that we have the same input since nothing in our function is non-deterministic. It’s just combining the parameters together and returning the result.

Also, we don’t have to worry about the current date or how the date implementation changes.

Extending the function is hard before we changed it to a pure function because of the unpredictability of the result of new Date() .

In addition, changing new Date() may break other parts of the program before out change. Now we don’t have to worry about that.

Because of the unpredictability, it’s also harder to trace and debug.

Avoid Long Argument List

With the destructuring syntax of ES6, we can pass in lots of arguments to a function without actually passing them all in separately.

For example, instead of writing:

const add = (a, b, c, d, e) => a + b + c + d + e;

which has 5 parameters. We can write:

const add = ({
  a,
  b,
  c,
  d,
  e
}) => a + b + c + d + e;

This way, we can just pass in one object into the function and get 5 variables inside the add function.

We can call add as follows:

const obj = {
  a: 1,
  b: 2,
  c: 3,
  d: 4,
  e: 5
}

add(obj);

This also works for nested objects. For example, we can write:

const buildString = ({
  foo: {
    bar,
    baz
  }
}) => `${bar} ${baz}`;

Then we can call buildString as follows:

const obj = {
  foo: {
    bar: 'bar',
    baz: 'baz'
  }
};

buildString(obj);

To keep data private, we can use modules and blocks to replace IIFEs. Now we don’t need extra code to define functions and call it.

Also, the class syntax is a much clearer way to define constructors, especially when we want to inherit from other constructors. The value of this is also clearer.

Writing pure functions is a good practice because it’s predictable since we always have the same outputs given the same inputs. It’s also easier to read and test because of it.

With the destructuring syntax, we can reduce lots of arguments to variables in an object. Everything is assigned automatically given the key name and position of the properties we pass in as the argument.

Categories
JavaScript Best Practices

JavaScript Best Practices — Things to Avoid

Like any other programming language, JavaScript has its own list of best practices to make programs easier to read and maintain. There are lots of tricky parts to JavaScript, so there are many things to avoid.

We can follow some best practices easily to make our JavaScript code easy to read.

In this article, we look at things to avoid, including declaring global variables whenever possible, passing strings to setInterval and setTimeout, the with statement, and more.


Avoid Declaring Global Variables

We should avoid the use of global variables as much as possible for various reasons.

One is that they’re easy to overwrite in different places since they’re available everywhere. They can also overwrite things in the window object since global variables are properties of the window object.

These two are real issues that make our code hard to follow. Therefore, we should define local variables as much as possible.

We can define local variables by using the var, let, or const keywords.

Variables defined with var are available at the level where it’s defined and below before it’s defined. For example, if we write:

const log = () => {
  console.log(x);
}
log();
var x = 1;
log();

Then we get undefined for the first console.log and one for the second log.

It’s the same as writing:

Variables declared with let are available only after they’re defined, so if we give:

const log = () => {
  console.log(x);
}
log();
let x = 1;
log();

We get the error:

Uncaught ReferenceError: Cannot access ‘x’ before initialization

With the const keyword, we define constants that can only be assigned once and never again. It’s also available only after it’s declared unlike var.

For example, we can write:

const log = () => {
  console.log(x);
}

const x = 1;
log();

Calling log before const x = 1 will also get us Uncaught ReferenceError: Cannot access ‘x’ before initialization.

If we want variables that are available in different parts of a program, we should use JavaScript modules and then build the modules into one or several large files when we release our code. This has been available since ES6.

We can export our variables and import them into other modules. There’s also export default to export the whole module. This way, we only export things that should be available outside the module and keep everything else private.

We can also use closures to keep variables inside a function so they can’t be accessed outside. An example of a simple closure would be:

const divide = () => {
  const x = 3;
  return () => x * 2;
}

We keep x inside the divide function so that it can’t be accessed outside and return a function that does something with it.

Then we call it by writing:

console.log(divide()());

Photo by Matt Jones on Unsplash


Never Pass a String to setInterval or setTimeout

We should never pass strings to the first argument of the setInterval or setTimeout functions instead of a callback function.

If we pass a string into it instead of a callback function, the browser will use eval to run the code inside the string, which is slow and it’s vulnerable to code inject attacks.

Therefore, there’s no reason unless we really need to run code that’s generated on the fly, which should be very, very rare.

Instead of writing:

setTimeout(
  "document.getElementById('foo').textContent = 'foo'", 1000
);

We should write:

setTimeout(() => {
  document.getElementById('foo').textContent = 'foo';
}, 1000);

Don’t Use the “with” Statement

On the surface, the with statement may seem like a good shortcut for accessing deeply nested properties of objects. For example, we can write:

Instead of:

However, it pollutes the global scope and creates ambiguity of whether baz is accessible outside the with statement. This is because we can also declare new variables inside the with statement:

We declared baz inside with, but it isn’t accessible outside.

Fortunately, it’s not allowed to be used in JavaScript strict mode so we don’t have to worry about using the with statement now.


var Keyword

With ES6, we have the block-scoped let and const keywords for declaring variables and constants respectively. We can use them to declare variables since they have a clear scope.

Variables declared with var has a confusing scope. It’s available outside of blocks when it’s declared in a block like an if block or a loop.

Something like:

for (let i = 0; i < 10; i++){
  var j = i;
}
console.log(j);

Works with var, but we would get an error if we replace var with let.

Because the scope is confusing, we should stop using var to declare variables.


Conclusion

When we write JavaScript, we want to avoid many older constructs that existed in early versions of JavaScript. They make our code hard to read and it’s easy to create bugs with it.

First, we want to avoid global variable declarations to avoid referencing and declaring things accidentally in the global scope. It also reduces the chance of modifying a global variable’s values accidentally.

We also should stop passing in code in a string instead of passing in a callback function to the first argument of the setTimeout and setInterval functions. This is because eval will be used to run the code stored in a string, which exposes our code to code injection attacks and it’s also slow.

Also, we should avoid the with statement because it creates variables in the global scope.

Likewise, because of the confusing scope of variables declared with the var keyword, we should avoid declaring variables with the var keyword.

Categories
JavaScript Best Practices

JavaScript Best Practices: More Things to Avoid

ike any other programming language, JavaScript has its own list of best practices to make programs easier to read and maintain. There are lots of tricky parts to JavaScript, so there are many things to avoid.

We can follow some best practices to make our JavaScript code easy to read.

In this article, we look at more constructs that we should avoid, including the misuse of the for...in loop, the == operator, the arguments object, and this.


Misusing the For…in Loop

The for...in loop shouldn’t be used to iterate through arrays and array-like objects. For example, if we have:

const arr = ['a', 'b', 'c', 'd', 'e'];
for (let i in arr) {
  console.log(i)
}

We get back:

0
1
2
3
4

However, this is a misuse of the for...in loop since it’s supposed to iterate through the enumerable properties of an object and its prototype rather than looping through the indexes of an array.

The order of the enumeration isn’t guaranteed, so we might not loop through the array in order, which is bad.

The for...in loop also loops through inherited properties that are enumerable, which is another problem.

For example, if we have:

Then we create an object with a prototype object with the foo property. The for...in loop will loop through both the enumerable properties of obj’s prototype and obj.

To avoid looping through the properties of its prototype, we can use Object.keys instead:

for (let prop of Object.keys(obj)) {
  console.log(prop);
}

We can loop through the key-value pair of obj without anything from its prototype, we can use Object.entries:

for (let entry of Object.entries(obj)) {
  console.log(entry);
}

For looping through arrays and array-like objects, we should use the for...of loop instead:

const arr = ['a', 'b', 'c', 'd', 'e'];
for (let a of arr) {
  console.log(a)
}

Then we get the entries of the array.


Messing With this

The this object is always a problem in JavaScript. This is because it changes depending on the scope.

Fortunately, in ES6, we have arrow functions that don’t change the value of this if we reference it inside. This also means that we can’t change the value of this with call, bind, or apply.

For example, the following code will log the window object:

The value of this doesn’t change even if we try to change it with bind.

Also, ES6 has the class syntax for creating constructor functions so it’s clear that this should be inside the class as long as we use class methods and arrow functions.

Class methods will have the class as the value of this and arrow functions inside the class will have the same value for this.

Photo by Casey Allen on Unsplash


The == Operator

The == operator does automatic type conversion before comparing the operands for equality. This creates issues with different types of data being considered the same.

For example, null == undefined returns true, which we probably don’t want.

null == undefined returns true because they’re both falsy.

To avoid these kinds of issues, we should use the === operator instead, which would avoid comparisons of these kinds of operands returning true. This is because === checks the type of each operand in addition to the operand’s contents.


The arguments Object

With the introduction of the rest operator, we can finally avoid the use of the arguments object for getting the arguments that were passed into a function.

The arguments object shouldn’t be used for a few reasons. One is that it’s an array-like object, which means properties like length are in it and it can be looped through with a for loop, but array methods like map and forEach aren’t included in it.

Also, we can access its entries with its index, which is deceiving since it’s not actually an array.

Using arguments also prevents any code optimization by browser engines, which means performance is slower. Also, the arguments object isn’t available in arrow functions.

Therefore, to access extra arguments that are beyond the parameters listed, we should use the rest operator instead.

For example, instead of writing:

We can write:

const add = (...args) => args.reduce((a, b) => a + b, 0);
console.log(add(1, 2, 3, 4, 5));

Notice that it’s much shorter to use the rest operator, which is the ...args part of the function signature. args is already an array so we have all the array methods available for us to use.


Conclusion

In modern JavaScript, we can abandon a lot of older constructs to make our code easier to read and maintain.

We can use the rest operator instead of the arguments object. This is because the rest operator gives us an array of arguments instead of the array-like arguments object.

Also, we should avoid the == operator for equality comparison since it does automatic type conversion before comparing which we might not want.

We should also avoid messing with this by using arrow functions and class syntax for constructor functions. These two constructs make the value of this a lot clearer. We only use class methods for classes and arrow functions for functions that aren’t a method of a class.

Finally, the for...in loop shouldn’t be used for looping through arrays and array-like objects since the order of enumeration isn’t guaranteed to be in order, leading to unexpected results.

Categories
JavaScript Best Practices

JavaScript Best Practices — Using Libraries and Shortcuts That are Clear

Like any other programming language, JavaScript has its own list of best practices to make programs easier to read and maintain. There are a lot of tricky parts to JavaScript, so there are things we should avoid that reduce the quality of our code. By following best practices, we can create elegant and manageable code that’s easy for anyone to work with.

In this article, we look at using libraries, using shortcuts that make sense, short ways to build a string from an array, and being aware of the difference between development and live code.

Use Libraries When They Exist

If somebody already made a library to do something that we want to do, we should use the library instead of writing everything from scratch again.

Reinventing the wheel is a time-consuming process, then testing all the cases takes even longer.

This means that we should use libraries to speed up software development and not reinvent the wheel.

Nowadays, client-side apps are more complex than ever, with users expecting slick user experiences in the apps we write. This means writing everything from scratch is pretty much impossible.

This is why frameworks like Angular, React and Vue are becoming more and more popular. There’s no going back to the bad old days where everything is written in plain JavaScript.

Front end development isn’t just adding a few things on top of our back end apps anymore.

However, when we’re learning JavaScript, we should still learn by writing plain JavaScript code first to learn the principles that make these libraries and framework work.

Nowadays, client-side apps are complex enough that it’s hard to avoid using lots of libraries to build something functional.

Difference Between Development Code and Live Code

The codebase that we read and write shouldn’t be mistaken for the code that’s run.

Most browsers optimize the code if we follow the best practices that are listed all over the online community.

Browsers convert the code we write to machine code on run-time to make it run faster.

Also, lots of client-side apps now have a build process associated with it to optimize the code by minifying it and combining redundant code into one place, etc.

This means that we should build code for readability and maintainability rather than trying to make hard to read code that we think will make it run faster.

Browsers no longer run code line by line from script files anymore.

Use Shorthand When They Make Sense

Some of the older shorthands in JavaScript are confusing. For example, if we run the following code:

if(false)  
   var x = false  
   let y = 0  
console.log(x, y)

We get undefined and 0 logged. undefined makes sense, but why is y 0? This is because the declaration of y actually doesn’t belong to the if block.

As we can see, the if statement without curly braces is confusing. Only the first line after the if statement is considered to be inside the if block.

Therefore, we should always wrap our if blocks with curly braces.

Another problem with the code snippet above is that we don’t have semicolons. This is another problem because when don’t know where the line ends. It’s better to just make it clear by putting in the semicolon at the end.

However, since ES6, we have lots of shorthands that are very useful and aren’t confusing.

For example, the spread and rest operators are very useful for copying or combining objects and getting arguments in an array respectively.

The spread operator can be used as follows:

const obj1 = { foo: 1, bar: 2 }, obj2 = { baz: 3 };  
const obj3 = { ...obj1, ...obj2 };

Then we get obj3 having the value:

{ foo: 1, bar: 2, baz: 3 }

The rest operator uses the same 3 dots, but works on functions. For example, we can write a function with the rest operator as follows:

const add = (a, b, ...rest) => {  
  return a + b + rest.reduce((a, b) => a + b, 0);  
}

Where ...rest is has an array with the arguments other than the first 2.

Then we can call it as follows:

console.log(add(1, 2));  
console.log(add(1, 2, 3));  
console.log(add(1, 2, 3, 4, 5));

And we get 3, 6, and 15 respectively.

Another handy and clear shortcut is the destructuring assignment syntax. It works on both objects and arrays. For example, we can destructure an array into variables as follows:

const [a, b] = [1, 2];  
console.log(a, b);

Similarly, with objects, we can write:

const {  
  foo,  
  bar  
} = {  
  foo: 1,  
  bar: 2  
};  
console.log(foo, bar);

We get 1 and 2 for a and b respectively in both examples.

Shortest Way to Building a String

There’re multiple ways to build a string from an array of strings in JavaScript. One way is to use a loop as follows:

const arr = ['foo', 'bar', 'baz'];  
let str = '';  
for (let i = 0; i < arr.length; i++) {  
  str += arr[i];  
  if (i !== arr.length - 1) {  
    str += ',';  
  }  
}

The other way is to use the join method built into array objects:

const arr = ['foo', 'bar', 'baz'];  
let str = arr.join(',');

As we can see, the loop is much more complex and are much more prone to errors because of it. We’ve to loop through the array, concatenate each entry, and then add a comma after it if it’s not the last entry.

On the other hand, the join method just joins the array entries with a comma with one line.

Today’s JavaScript apps are more complex than ever. This is why we should use libraries and frameworks to build apps to make our lives easier.

This also means that we should be writing code that makes development easy rather than thinking about how to accommodate browsers. However, we should still think about performance when writing the code.

Also, we should also use shortcuts that makes development and reading the code easy. Most shortcuts introduced with ES6 or later have both of these characteristics. Older shortcuts like writing if statements without curly braces are not.

Finally, we should use existing methods to simplify code. For example, when making a comma-separated string from an array of strings, we can use the join method instead of writing a loop to do it.

Categories
JavaScript Best Practices

JavaScript Best Practices — Browser Differences, Performance and More

Like any other programming language, JavaScript has its own list of best practices to make programs easier to read and maintain. There are a lot of tricky parts to JavaScript, so there are things we should avoid that reduce the quality of our code. By following best practices, we can create elegant and manageable code that’s easy for anyone to work with.

In this article, we’ll look at how to gracefully support old browsers, avoiding heavy nesting, various optimizations, and checking the data in our programs.

Progressive Enhancement

Progress enhancement means that we should degrade our app gracefully when some technology isn’t available. This means that we should check if some technology is supported in all the browsers that we want to support and keep our app working in some way if it doesn’t.

We can also add polyfills to add support for new technologies that older browsers that we support don’t have.

For example, if we want to use new array methods that aren’t available in Internet Explorer 11 but our app still supports the browser, then we have to add a polyfill for it or check for its existence and do something different instead of crashing.

Avoid Heavy Nesting

Having lots of nesting in code makes them very confusing to read. This is because it’s very hard to follow the logic of the code.

Nesting conditional statements and loops should be kept to a minimum.

For example, instead of writing:

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

const parentUl = document.createElement('ul');
for (const item of Object.keys(items)) {
  const parentLi = document.createElement('li');
  const childUl = document.createElement('ul');
  for (const num of items[item]){
    const childLi = document.createElement('li');
    childLi.textContent = num;
    childUl.appendChild(childLi);
  }
  parentLi.textContent = item;
  parentLi.appendChild(childUl);
  parentUl.appendChild(parentLi);
}
document.body.appendChild(parentUl);

This creates a nested list, which is confusing to read and write. We should reduce nesting by separating the list creating into a function and call the function instead:

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

const createUl = (items) => {
  const ul = document.createElement('ul');
  for (const item of items) {
    const li = document.createElement('li');
    li.textContent = item;
    ul.appendChild(li);
  }
  return ul;
}

const parentUl = createUl(Object.keys(items));
const parentLis = parentUl.querySelectorAll('li');
for (const parentLi of parentLis) {
  const childUl = createUl(items[parentLi.textContent]);
  parentLi.appendChild(childUl);
}
document.body.appendChild(parentUl);

As we can see, the code above doesn’t have any nested loops, which makes the code easier to read. Also, we have a createUl function to create the ul element with entries inside and returns the ui element object.

This means that we can attach it however we like to the document or an HTML element afterward.

Optimize Loops

We should cache values that are used in every literation in a single variable.

This is because every time we do this, the CPU has to access the item in memory again and again to compute its results.

Therefore, we should do this as little as possible.

For example, if we have a loop, we shouldn’t write the following:

for (let i = 0; i < arr.length; i++) {

}

Instead, we should write:

let length = arr.length;
for (let i = 0; i < length; i++) {

}

This way, arr.length is only referenced once in our loop instead of accessing it in every iteration.

Photo by Adrien CÉSARD on Unsplash

Keeping DOM Access to a Minimum

DOM manipulating is a CPU and memory-intensive operation. Therefore, we should strive to keep it to a minimum.

This means we have to keep pages as simple as possible and only do DOM manipulation when it’s necessary. Any static styles should be in CSS and not added on the fly with JavaScript.

Also, we should keep any static elements in HTML and not create them by manipulating the DOM.

Also, we should make functions that create elements and call them when we need to rather than continuously doing DOM manipulating operations on the top-level of the code.

Write Code for All Browsers

All browsers should get the same treatment by our code. We shouldn’t write hacks to accommodate various browsers because these hacks will be broken quickly when the browser changes versions.

We should stick to code that’s are accepted as standards or use libraries like Modernizr to deal with issues with different browsers.

Also, we can add polyfills to add any functionality that is missing in various browsers, so we can keep our app running on different browsers even though there’s they might not support some functionality out of the box.

Don’t Trust Any Data

We should check for any data that’s inputted by the user. HTML5 has lots of form validation functionality to check for valid inputs. We can do it with HTML5 and plain JavaScript.

Once we check for the inputted data, we also need to check for data in variables and returned from functions. Since JavaScript is a dynamically typed language, we’ve to check for these things.

For primitive values, we can use the typeof operator to check the data type of data. For example, if we have:

let x = 1;

Then typeof x will return 'number' . Other primitive data types like boolean, strings, undefined, etc. are the same.

The only exception is null , which has the type object.

We should always check for values like null or undefined since they might crash our program. We can do that by writing x === null and typeof x === 'undefined' respectively.

Also, we should be careful of type coercions done by JavaScript, like in conditional statements and function calls. For example, the Math.min method converts its arguments to numbers when it’s called. The == operator converts all the operands to the same type before returning the result.

For objects, we can check for their type by using the instanceof operator to see which constructor they’re created from. For example, if we have an array:

let arr = [];

Then [] instanceof Array will be true . Arrays also have a static isArray method to check for the data type.

In the JavaScript code, we write, we should be aware of the differences in different browsers that our app supports. This means checking if the methods that we want to use exist and adding polyfills to add missing functionality for older browsers.

We should cache variables and properties that are accessed repeatedly in loops so they don’t have to be accessed each time the loop runs.

Deep nesting should also be avoided to keep code clear and easy to follow.

Also, since DOM manipulation is an expensive operation, it should be kept to a minimum. Static styles and elements should be in CSS and HTML respectively.

Finally, we shouldn’t trust the data in our apps. Inputs have to be checked for format and validity, and data in our variables and values should be checked for type, including null or undefined .