We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies.

We use cookies and other tracking technologies to improve your browsing experience on our site, analyze site traffic, and understand where our audience is coming from. To find out more, please read our privacy policy.

By choosing 'I Accept', you consent to our use of cookies and other tracking technologies. Less

We use cookies and other tracking technologies... More

Login or register
to apply for this job!

Login or register to start contributing with an article!

Login or register
to see more jobs from this company!

Login or register
to boost this post!

Show some love to the author of this blog by giving their post some rocket fuel 🚀.

Login or register to search for your ideal job!

Login or register to start working on this issue!

Engineers who find a new job through JavaScript Works average a 15% increase in salary 🚀

Blog hero image

How to keep your JavaScript code simple and increase its readability

Leonardo Lima 16 February, 2018 | 4 min read

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.


    Originally published on medium.freecodecamp.org

    Related Jobs

    Related Issues

    viebel / klipse-clj
    viebel / klipse-clj
    • Open
    • 0
    • 0
    • Intermediate
    • Clojure
    viebel / klipse
    • Open
    • 0
    • 0
    • Intermediate
    • Clojure
    viebel / klipse
    • Open
    • 0
    • 0
    • Intermediate
    • Clojure
    • $100
    viebel / klipse
    • 1
    • 0
    • Intermediate
    • Clojure
    viebel / klipse
    • Open
    • 0
    • 0
    • Intermediate
    • Clojure
    • $80
    viebel / klipse
    • Open
    • 0
    • 0
    • Advanced
    • Clojure
    • $80
    viebel / klipse
    • Started
    • 0
    • 2
    • Advanced
    • Clojure
    • $180
    viebel / klipse
    • Open
    • 0
    • 0
    • Intermediate
    • Clojure
    viebel / klipse
    • Started
    • 0
    • 1
    • Intermediate
    • Clojure
    • $80

    Get hired!

    Sign up now and apply for roles at companies that interest you.

    Engineers who find a new job through JavaScript Works average a 15% increase in salary.

    Start with GithubStart with Stack OverflowStart with Email