We're planting a tree for every job application! Click here to learn more

How to keep your JavaScript code simple and increase its readability

Leonardo Lima

28 Feb 2018

4 min read

How to keep your JavaScript code simple and increase its readability
  • JavaScript

After a few years working almost exclusively with Ruby on Rails and some jQuery, I changed my focus to front-end development. I discovered the beauties of JavaScript ES6 syntax and the exciting modern libraries such as React and Vue. I started to implement new features using nothing but ES6 Vanilla JavaScript, and fell instantly in love with all this new class abstraction and those arrow functions.

Nowadays, I’m generating large amounts of JavaScript code. But, since I consider myself a JavaScript Padawan, there’s yet a lot of room for improvement. Through my studies and observations, I’ve learned that even with the use of syntactic sugars featured in ES6, if you don’t follow the main principles of SOLID, your code has a high chance of becoming complex to read and maintain.

To demonstrate what I’m talking about, I’ll walk you through one fantastic Code Review session I had last week with with a buddy of mine. We are going to start with a 35-lines JavaScript Class and will finish with a beautiful 11-lines code piece using only slick functions. With patience and resilience, you will be able to observe and apply the pattern to your own codebase.

The Feature

What I needed to do was quite simple and trivial: get some information from the page and send a request to a third-party tracking service. We were building an event tracker on the client side and tracking some page actions along with it.

The code examples below implement the same task using different code design tactics.

Day 1 — Using ES6 Class syntax (aka Object Prototype Pattern wrapper)

import SuccessPlanTracker from './success-plan-tracker';
import TrackNewPlanAdd from './track-new-plan-add';

class EmptyIndexTracking {
  constructor(dataset) {
    this.trackingProperties = dataset;
    this.emptyIndexButtons = [];
  }

  track(element) {
    const successPlanTracker = new SuccessPlanTracker(this.trackingProperties);
    const emptyIndexProperty = {
      emptyIndexAction: element.dataset.trackingIdentifier,
    };

    successPlanTracker.track('SuccessPlans: EmptyIndex Interact', emptyIndexProperty);
  }

  bindEvents() {
    this.emptyIndexButtons = Array.from(document.getElementsByClassName('js-empty-index-tracking'));

    this.emptyIndexButtons.forEach((indexButton) => {
      indexButton.addEventListener('click', () => { this.track(indexButton); });
    });
  }
}

document.addEventListener('DOMContentLoaded', () => {
  const trackProperties = document.getElementById('success-plan-tracking-data-empty-index').dataset;

  new EmptyIndexTracking(trackProperties).bindEvents();
  new TrackNewPlanAdd(trackProperties).bindEvents();
});

export default EmptyIndexTracking;



You can notice above that I started smart: isolating the generic tracker SuccessPlanTracker to be reused in another page besides the Empty Index.

But, wait a minute. If this is an empty index tracker, what on earth is this foreigner TrackNewPlanAdd doing there?

Day 2 — Getting rid of Class boilerplate code

import SuccessPlanTracker from './success-plan-tracker';

let emptyIndexButtons = [];
let emptyIndexTrackingData = {};
let emptyIndexActionProperty = {};
let emptyIndexTrackingProperties = {};

const trackEmptyIndex = (properties) => {
  const successPlanTracker = new SuccessPlanTracker(properties);
  successPlanTracker.track('SuccessPlans: EmptyIndex Interact', properties);
};

const populateEmptyIndexData = () => {
  emptyIndexButtons = document.querySelectorAll('.js-empty-index-tracking');
  emptyIndexTrackingData = document.getElementById('success-plan-tracking-data-empty-index').dataset;
};

const bindEmptyIndexTracker = () => {
  populateEmptyIndexData();
  emptyIndexButtons.forEach((indexButton) => {
    indexButton.addEventListener('click', () => {
      emptyIndexActionProperty = { emptyIndexAction: indexButton.dataset.trackingIdentifier };
      emptyIndexTrackingProperties = { ...emptyIndexTrackingData, ...emptyIndexActionProperty };
      trackEmptyIndex(emptyIndexTrackingProperties);
    });
  });
};

export default bindEmptyIndexTracker;

Okay, now the file name clearly reflects the feature responsibility and, look at that, there is no more EmptyIndexTracker class giving us less boilerplate code. Learn more here and here. We are using simple functions variables and, man, what a good catch using those shining ES6 Object Spread dots.

I find out that the querySelectorAll method already returns an array, so we are able to remove the Array.from() function from Array.from(document.getElementsByClassName('js-empty-index-tracking')) Remember that getElementsByClassName method returns an object.

Also, since the central responsibility is to bind HTML elements, the document.addEventListener('DOMContentLoaded') function invocation doesn’t belongs to the file anymore.

Good job!### Day 3 — Removing ES5 old practices and isolating responsibilities even more.

import successPlanTrack from './success-plan-tracker';

export default () => {
  const buttons = document.querySelectorAll('.js-empty-index-tracking');
  const properties = document.getElementById('success-plan-tracking-data-empty-index').dataset;

  buttons.forEach((button) => {
    properties.emptyIndexAction = button.dataset.trackingIdentifier;
    button.addEventListener('click', () => {
      successPlanTrack('SuccessPlans: EmptyIndex Interact', properties);
    });
  });

  return buttons;
};

If you pay close attention, there is no SuccessPlanTracker class in the code above — it suffered the same fate as the old EmptyIndexTracker. The class-killing mindset, once installed, spreads and multiplies itself. But don’t fear, my good lad.

Remember, always try to keep your JavaScript files simple. There is no need to know about the states of class instances, and the classes were exposing practically only one method.

Don’t you think using the ES6 class abstraction was a little bit of overkill?

Did you also notice that I removed the variables instances from the top of the file? This practice remounts to ES5, and we don’t need to worry so much about it now that we have ES6+ syntax.

Finally, the last major change in this third version. Our empty index tracker binder now does only one thing: elements binding.

Following those steps brought the code very close to the Single Responsibility Principle — one of the most important SOLID principles.

Day 4 — Avoiding DOM sloppy manipulation

import successPlanTrack from './tracker';

const trackAction = (properties, button) => {
  const trackProperties = { ...properties, emptyIndexAction: button.dataset.trackingIdentifier };
  successPlanTrack('SuccessPlans: EmptyIndex Interact', trackProperties);
};

export default () => {
  const buttons = document.querySelectorAll('.js-empty-index-tracking');
  const dataset = document.getElementById('success-plan-tracking-data-empty-index').dataset;
  const properties = { ...dataset, emptyIndexAction: '' };

  buttons.forEach(button => (
    button.addEventListener('click', () => trackAction(properties, button))
  ));

  return buttons;
};

Hey, there are more lines now, you liar!

The thing is that our third version was a little broken. We were inappropriately mutating DOM Elements datasets in the line properties.emptyIndexAction = button.dataset.trackingIdentifier;.

The property of one button was being passed to another button, generating messed up tracking events. To solve this situation, we removed the responsibility of assigning the emptyIndexAction property from the binding loop to a proper function by creating its own scoped method trackAction().

By adding those extra lines, we improved our code, better encapsulating each action.

Finally, to wrap up and write down

  • If you want to design and write marvelous pieces of code, you need to be willing to explore further and go beyond the limits of a proper and modern syntax.
  • Even if the first version of your code ended up being simple and readable, it doesn’t necessarily mean that the system has a good design or that it follows at least one of the SOLID principles.
  • It’s essential to accept constructive code reviews and let other developers point out what you can do better.
  • To keep your code simple, you need to think bigger.
  • ProTip to-go: Here’s a very useful ES6 (ES2015+) cheatsheet


    Find more from the author Leonardo Lima at his portfolio or his twitter


    If you’re passionate about Front End development, check out the JavaScript Works job-board here.


    Did you like this article?

    Related jobs

    See all

    Title

    The company

    • Remote

    Title

    The company

    • Remote

    Title

    The company

    • Remote

    Title

    The company

    • Remote

    Related articles

    JavaScript Functional Style Made Simple

    JavaScript Functional Style Made Simple

    Daniel Boros

    12 Sep 2021

    JavaScript Functional Style Made Simple

    JavaScript Functional Style Made Simple

    Daniel Boros

    12 Sep 2021

    WorksHub

    CareersCompaniesSitemapFunctional WorksBlockchain WorksJavaScript WorksAI WorksGolang WorksJava WorksPython WorksRemote Works
    email iconhello@works-hub.comUK flag

    Ground Floor, Verse Building, 18 Brunswick Place, London, N1 6DZ

    US flag

    108 E 16th Street, New York, NY 10003

    Subscribe to our newsletter

    Join over 111,000 others and get access to exclusive content, job opportunities and more!

    © 2023 WorksHub

    Privacy PolicyDeveloped by WorksHub