Wednesday, 10 May, 2017 UTC


Summary

JavaScript is one if not the most popular programming language in the world. Found in web pages, software, mobile apps, console applications, etc.
JavaScript is everywhere and it is definitely here to stay. The problem with this language is with its rise, a lot of applications that use it feel "janky" (slow). We are here today to try and optimize (reduce jank in) our JavaScript applications.
Throttling & Debounce
When we add event listeners to user actions like a scroll, we ignore the fact that listener(s) fire when our events get triggered. This is a potential bottleneck for our JavaScript application.
Take the scroll event for example, when we do something like this
function scrollHandler() {
    return console.log('yippeeeee');
}

window.addEventListener('scroll', scrollHandler());
Everytime a user scrolls, "yippeeeee" gets logged to the console.
This doesn't seem bad, but with more expensive operations like checking if an element is in the viewport so we can animate, this becomes expensive with time and uses more memory.
A simple way to fix this is to either debounce or throttle the scrollHandler function. Wait, what?
It sounds complicated, but:
Debouncing enforces that a function not be called again until a certain amount of time has passed without it being called. As in "execute this function only if 100 milliseconds have passed without it being called."
while
Throttling enforces a maximum number of times a function can be called over time. As in "execute this function at most once every 100 milliseconds."
A quick example, using Lodash to debounce our application is
window.addEventListener('scroll', _.debounce(scrollHandler, 300));

// or

window.addEventListener('scroll', _.throttle(scrollHandler, 300));
Another library I'll recommend is Underscore. You can either use one of the libraries above or you can rip the functions out of the codebase and add to yours.
Switch to HTTP/2 and don't bundle
HTTP/2 is beginning to rise and unlike before where the best advice we got when serving JavaScript files was to combine our files into a large bundle, now we don't need to do that anymore.
HTTP/2 is multiplexed meaning that it's built to handle many file requests in a more optimal form. HTTP/2 can use one TCP connection to request many files unlike HTTP/1 where we had separate connections for each request. This makes HTTP/2 a lot faster when compared to HTTP/1 where we could only request files one at a time.
Thus serving up many files is now a good thing, as long as you are using HTTP/2. But not too much (too much of anything is bad).
Trim the fat
If you use NPM to install dependencies, then you have a lot of junk in your build file. From a meme of Guy Fieri to localization files you "may" use.
Instead of including the entire codebase to use a part of it, just include what you need. Take Lodash for example, it comes with a lot of functions we may use, instead of keeping what we "may" use, why not require what we want.
Like this:
// Load the full build.
var _ = require('lodash');

// Load only the array helper.
var arr = require('lodash/array');

// Load only the debounce function.
var debounce = require('lodash/debounce');
Doing this will reduce file size and speed up parsing by the browser.
Minify
This has been one of most talked about JavaScript optimization technique ever.
Minification refers to the removal of unwanted/cumbersome elements from our JavaScript source.
Unwanted refers to comments, semi-colons, whitespace etc. While cumbersome refers to shortening function and variable names, reducing an if-else into ternary etc.
We can achieve minification with build tools like UglifyJs, Google Closure compiler or online tools like JS Compress, JS minifier etc.
Async & Defer
async and defer are attributes we can add to script tags to make them either load asynchronously to the page or defer until the page has completely loaded.
Doing either async or defer doesn't block the DOM from rendering, which makes the application feel faster.
<!-- load meh.js asynchronously to the page -->
<script source="meh.js" async></script>

<!-- load meh.js after the page has loaded -->
<script source="meh.js" defer></script>
Recycle/Reduce the Dom
Look at Facebook, Twitter, Instagram, 9gag, the list goes on when it comes to websites with infinite scrolling enabled. The problem with infinite scrolling is that the more dynamic content you add to the DOM, the more elements we have to query. This is bad for performance and quickly slows the browser by consuming memory (RAM).
9gag's approach to solving the problem of too many elements is quite cool. After reading a certain number of memes, 9gags shows you a button you can use to clear out memes you passed. If you click on the button, all the previous memes get deleted from the page and you begin to notice the site speed up.
9gag's solution is stellar, but another thing you can do is recycle DOM elements. You allocate some elements to use for displaying content to the DOM and when that elements exit the viewport, you take it back to the bottom as illustrated below.
requestAnimationFrame
There's this myth that JavaScript animations are slow. The truth is they are if not properly written same goes for CSS animations. But we can change that.
With requestAnimationFrame, we tell the browser to call a function to render a frame. The reason why this is so performant when compared to something like setTimeout is because it triggers the GPU for a faster and better rendering.
Thus making our animation render at 60fps (or a frame every 16.667ms) which is just perfect for the human eyes when it comes to speed it can comfortably understand.
To use it.
var start = null;
var element = document.getElementById('SomeElementYouWantToAnimate');
element.style.position = 'absolute';

// Takes in the timestamp of the current frame
function step(timestamp) {
    if (!start) start = timestamp;

    // calculate total time passed
    var progress = timestamp - start;

    // get the number of pixels the element should move every frame
    element.style.left = Math.min(progress / 10, 200) + 'px';

    // only animate if time passed is lesser than 2000ms or 2 seconds
    if (progress < 2000) {
        window.requestAnimationFrame(step);
    }
}

window.requestAnimationFrame(step);
This feels like too much work just to make a box move left. But there are JavaScript libraries like Anime.js, Gsap that make animation a breeze.
Go Offline
One use of JavaScript service workers is to cache files for use offline. The thing is that the files are not only cached to be used offline, they can also be used when there is an internet connection. This helps your application skip the request for a JavaScript file and just fetch it from its cache which is definitely faster and better optimised when compared to fetching the file from the server.
Also, going offline gives your web app the feel of a desktop or mobile app (depending on the screen) which gives the illusion of a faster application.
Use Promises
JavaScript promises use a fluent API to describe code and being a native function, they are completely optimised and should be used frequently. The fact that promises are asychronous means they are not blocking thus improving the speed of your application
Profile your code
Chrome developer tools is a very robust range of tools. Apart from a console, DOM inspector, it also comes with a profiler. Chrome developer tools is a very robust set. Apart from a console, DOM inspector, it also comes with a profiler (learn about Chrome's JavaScript Profiler).
Soon to be called "Memory Panel", this tool checks runs a series of tests on your web application and looks for memory leaks. If it discovers anything, it is then displayed in a graph, showing you potential bottlenecks and memory leaks.
After you fix these problems, you will definitely notice an improvement in your app performance and prevent JavaScript from quitting on you :).
Conclusion
These are just some ways to make your JavaScript application faster. I'm sure there are other ways to optimise JavaScript for performance, let us know down in the comments.
Sources:
  • window.requestAnimationFrame() on MDN (Mozilla Developer Network)
  • The Difference Between Throttling and Debouncing
  • “Offline First” with Service Worker (Building a PWA, Part 1)
  • Complexities of an Infinite Scroller