Categories
JavaScript Best Practices

Maintainable JavaScript — JavaScript/HTML Coupling

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at loose coupling between JavaScript and HTML.

Keep JavaScript Out of HTML

We should keep JavaScript out of our HTML code.

It’s hard to debug JavaScript code that’s in our HTML files.

For instance, if we have:

<button onclick="doSomething()">click me</button>

then we call our doSomething function when we click on the button.

doSomething maybe in an external file or in a script tag.

We may run into issues when we click the button before the function is available, which causes a JavaScript error.

The resulting error may pop up and cause the button to appear to do nothing.

It’s also harder to maintain since changing the name would mean we’ve to also look for the name in the HTML file.

We may also want to change the code in the onclick attribu

Embedding JavaScript code in HTML makes changing the code easier.

Most of our JavaScript code should be in external files and included on the page with a script tag.

The on attributes shouldn’t ve used for attaching event handlers to HTML.

Instead, we should attach the event listeners to the element by getting the element with JavaScript and attaching the listener:

function onClick() {
  // ...
}

const btn = document.querySelector("button");
btn.addEventListener("click", onClick, false);

We set the onClick function as the click handler for the button.

The benefit of that is that onClick is defined in the same files as the code that attaches the event handler.

If the function needs to change, then we need to change the JavaScript file.

If the button should do something else when it’s clicked, then we just need to change the code in the file.

IE8 and earlier don’t have the addEventListener method.

Instead, we have to use the attachEvent method.

With jQuery, we can write:

$("button").on("click", onClick);

We can also embed JavaScript in HTML with a script element that has inline code.

But this isn’t good since it keeps the JavaScript code in the HTML and makes the code longer.

For instance, we shouldn’t write code like:

<script>
  doSomething();

</script>

It’s best to keep all JavaScript in external files and keep inline JavaScript out of our HTML.

This helps us debug the code easier.

When a script error occurs, we know the error is in a JavaScript file.

If the JavaScript code is in our HTML file, then we have to search through both kinds of files.

Conclusion

We should keep our JavaScript code out of our HTML code so that we can debug them easier.

Categories
JavaScript Best Practices

Maintainable JavaScript — IIFEs and Events

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at avoiding global variables and loosen coupling in our UI.

No Global Approach

We can create IIFEs to create private variables without any global variables.

We pass the global objects we need into the IIFE and we can use it without changing it.

For instance, we can write:

(function(win) {
  const doc = win.document;
  // ...
}(window));

We create our IIFE and then called with the window object so that we can access window.document and assign it to a variable.

Then we can do whatever we want with it.

The function wrapper can be used for scripts where we don’t want to create any global objects.

This pattern is of limited use.

Any scripts that are used by other scripts on the page can’t use this approach.

If we have small scripts that don’t interact with any other scripts, then this approach can be used.

Event Handling

Event handling is doing all the time in client-side JavaScript.

Our app has to handle events from inputs, mouse clicks, scrolling and more.

There are also lots of potentials to create tightly coupled code if we aren’t careful.

The event object is available in the event handler functions as a parameter.

And we can easily use it to create tightly coupled code.

To make sure we don’t create tightly coupled code, we should keep a few things in mind.

Separate Application Logic

We need to separate app logic so that we don’t create app logic code that’s coupled to the UI.

App logic code that’s in the event handlers are hard to trace and test.

Also, we may end up duplicating our logic code when we can make them reusable.

And testing is harder is we create logic code that’s coupled to the event handling code.

We’ve to fire events to test our app logic code even when we just want to test the logic.

Therefore, we should separate the app logic code from the event handling code.

For instance, we can separate our app logic code from our event handles by writings:

function onClick(event) {
  showPopup(event);
}

function showPopup(event) {
  const popup = document.getElementById("popup");
  popup.style.left = `${event.clientX}px`;
  popup.style.top = `${event.clientY}px`;
  popup.className = "popup";
}

const button = document.querySelector('button');
button.addEventListener('click', onClick);

We have the showPopup function to show the popup and the onClick method to listen to clicks on the button.

When onClick runs showPopup runs so that we don’t have all our logic in our event handler function.

Don’t Pass the Event Object Around

We shouldn’t pass the event object around since it depends on the element that the event handler function is called on.

We want to decouple our app logic from the DOM elements, so we shouldn’t take the event object as a parameter in our app logic function.

Instead of taking the event object in showPopup , we should just take x and y coordinates.

For instance, we can write:

function onclick(event) {
  const {
    clientX,
    clienty
  } = event;
  showPopup(clientX, clienty);
}

function showPopup(x, y) {
  const popup = document.getElementById("popup");
  popup.style.left = `${x}px`;
  popup.style.top = `${y}px`;
  popup.className = "popup";
}

const button = document.querySelector('button');
button.addEventListener('click', onClick);

We changed the showPopup function to take the x and y coordinate of the popup so that we don’t have to pass the event object to our app logic.

This keeps our event handling code loosely coupled from the app logic code.

Conclusion

We should decouple our app logic code from our event handling code.

Also, we can create IIFEs to get global variables and use them inside.

Categories
JavaScript Best Practices

Maintainable JavaScript — Global Variables

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at avoiding global variables.

Avoid Globals

JavaScript’s execution environment is unique in that we used to use lots o global variables and functions.

The default execution environment of JavaScript is to use global variables everywhere.

Everything we have is defined as properties of the global object.

It’s an object that represents the outermost context for a script.

The window is the global object in the browser.

So any variable or function declared in the global scope becomes the property of the window object.

For instance, we have:

var color = "red"

or

function getColor() {  
  console.log(color);  
}

Then we can get the values by using:

console.log(window.color)

or:

window.getColor();

However, it’s a bad practice to create global variables everywhere since they have many issues.

One issue that it has is naming collisions.

Since everything is in the same scope, it’s likely that we have naming collisions within the global scope.

We may have defined the color global variable somewhere else.

This will overwrite the value of the one that’s defined earlier.

The getColor variable depends on color , so it would hard to track down the actual value of color .

We don’t know where color comes from.

Also, we run the risk that the global variable may become a built-in browser global variable later on.

Code Fragility

Having global variables makes the code tightly coupled to the environment.

If the environment changes, then the function is likely to break.

The getColor method logs undefined if the color variable no longer exists.

This means that any change to the global environment is capable of causing errors throughout the code.

Globals can be changed at any point by any function.

This means the reliability of global variables is also suspect.

To make our code more robust, we should avoid global variables.

So we instead of logging color directly in our getColor function, we should log the color from a parameter instead.

For instance, we can write:

function getColor(color) {  
  console.log(color);  
}

to get the color from a parameter instead.

This way, we know where it comes from.

Difficulty Testing

Having global variables also makes our code hard to tests.

Creating tests is very difficult if they rely on global variables since they can be changed by anything.

The tight coupling between different parts of the code caused by global variables causes unpredictable results.

Therefore, we got to fix this by removing the dependency on global variables.

We shouldn’t create our own global variables.

But we can rely on globals that are native to JavaScript like Array or Date .

Conclusion

We should avoid global variables as much as we can.

They make our code hard to test.

And the code that we create is fragile since the global variables can be changed anywhere by anything.

Categories
JavaScript Best Practices

Maintainable JavaScript — Functions and Arrays

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at how to check for functions, arrays, and bad ways to check for object properties.

Detecting Functions

Functions are reference types in JavaScript.

They’re created from the Function constructor of which each function is an instance.

There’s a bad way to check for a function.

We shouldn’t check for functions with the instanceof operator.

For instance, we shouldn’t write:

function func() {}

console.log(func instanceof Function);

to check for a function.

This isn’t good because it doesn’t work across frames since each frame have their own Function instance.

Instead, we should use the typeof operator.

So we should write:

function func() {}

console.log(typeof func === 'function');

typeof works across all frames.

Only IE8 or earlier have limitations with this, so we don’t have to worry about it.

Detecting Arrays

Arrays are also objects, so we can check for arrays with the instanceof operator.

For instance, we can write:

arr instanceof Array

But like any other uses of the instanceof operator, it won’t work across frames.

Another way that was recommended by Douglas Crockford was to check for array methods.

For instance, we can write;

function isArray(value) {
  return typeof value.sort === "function";
}

to check whether the sort method exists in the value object.

But anything can have the sort method so it’s probably not very accurate.

Another suggestion is to call the toString method on the value to see if it returns '[object Array]' .

So we can write:

function isArray(value) {
  return Object.prototype.toString.call(value) === "[object Array]";
}

This works across all frames and only arrays return this string, so this can be used to check for array.

However, now there’s a built-in way to check for arrays with the Array.isArray method.

For instance, we can write:

function isArray(value) {
  if (typeof Array.isArray === "function") {
    return Array.isArray(value);
  } else {
    return Object.prototype.toString.call(value) === "[object Array]";
  }
}

We check if Array.isArray exists by checking whether it’s a function.

Then we can call it to check if it’s an array.

Array.isArray works across all frames, so we can use it to check for arrays anywhere.

This is implemented in IE9, Firefox 4+, Chrome, Opera 10.5+, and Safari 5+.

This means that we can just use Array.isArray and forget about everything else.

Bad Ways of Detecting Properties

Detecting properties is another tricky issue we may run into.

There are a few bad ways to check for properties in an object.

They include:

if (object[propertyName]) {
  //...
}

if (object[propertyName] != null) {
  //...
}

if (object[propertyName] != undefined) {
  //...
}

All of them check for all falsy values, so they won’t work well for our needs.

Falsy values include 0, null , undefined , '' (empty string)m and false .

So those expressions will all be true if objec[propertyName] is any of those values.

Conclusion

We can check for arrays with Array.isArray .

And we shouldn’t check for all falsy values to check if an object property exists.

We can detect functions with the typeof operator.

Categories
JavaScript Best Practices

Maintainable JavaScript — Function Invocation and Equality

Creating maintainable JavaScript code is important if want to keep using the code.

In this article, we’ll look at the basics of creating maintainable JavaScript code by looking at variables and functions.

Function Invocation

We should wrap our immediate function invocations with parentheses so that we can tell them apart from function declarations.

For instance, we should write:

const doSomething = (function() {
  //...
  return {
    message: "hello"
  }
})();

We surround the functions with parentheses so that we know we’re calling it.

Strict Mode

ES5 introduced strict mode, which changes how JavaScript is executed and parsed to reduce errors.

To enable strict mode, we add 'use strict' above the code we want to enable strict mode on.

It’s a common recommendation to avoid placing 'use strict' in the global scope.

This is because if we’re concatenating multiple files into one, then we enable strict mode in all of them.

There’s a good chance that we’ll have errors in our old code if we have strict mode on all the code.

So before we fix all the code to follow strict mode, we should enable strict mode partially.

For instance, instead of writing:

"use strict";

function doSomething() {
  // ...
}

We should write:

function doSomething() {
  "use strict";
  // ...
}

If we want to enable strict mode on multiple functions, we should write:

(function() {
  "use strict";

  function doSomething() {
    // ...
  }

  function doMoreWork() {
    // ...
  }
})();

This is good since we keep strict mode within the function.

New code should always have strict mode on.

It corrects many mistakes like accidentally assigning to built-in global variables and other things.

Modules always have strict mode by default, so we always have to follow it.

Arrow Functions

Arrow functions are a newer kind of function introduced with ES6.

It’s great for defining functions that aren’t constructors since they don’t bind to their own this and doesn’t have its own instance methods.

Another benefit is that it doesn’t bind to the arguments object so that we can’t use it to get the arguments of the function call.

For instance, we can write:

const doSomething = () => {
  // ...
}

to define an arrow function.

Since they don’t bind to their own this value, they’re great for callbacks.

Equality

Equality with == or != is tricky because of type coercion.

Type coercion causes variables of a specific type to be converted automatically for an operation to succeed.

This can lead to some unexpected results.

== and != will do type coercion to its operands when we use them.

If the values don’t have the same data type, then type coercion will be done to one or both operands.

There’re many cases where they don’t do what we expect.

For example, we have:

console.log(5 == "5");

and logs true .

And:

console.log(25 == "0x19");

also returns true .

This is because type coercion is done with the Number function.

The strings are converted to numbers before doing the comparison.

This is one reason to avoid using == and != for comparisons.

Instead, we use === and !== .

Conclusion

We should be careful with function invocations.

Also, arrow functions are great for callbacks.

And comparisons should be done with === and !== .