diceline-chartmagnifiermouse-upquestion-marktwitter-whiteTwitter_Logo_Blue

Today I Learned

JavaScript reduce function performance enhancement

Time complexity of implemented algorithms is an aspect which for sure should be taken into consideration when working on websites with big amounts of data. In this manner, let us test out the performance of the mother and father of all JavaScripts higher order functions - Array.prototype.reduce.

Problem description: Our API provides us with an array of objects that looks something like:

const objects = [
   { id: 1, name: "name 1"},
   { id: 2, name: "name 2"},
   { id: 3, name: "name 3"},
]

and we want to create a lookup table out of it, like this:

{
   1: "name 1",
   2: "name 2",
   3: "name 3",
}

using reduce:

const lookup = objects.reduce((lookup, object) => ({
     ...lookup,
     [object.id]: object.name
}), {})

It works, but the time complexity of this algorithm is (unexpectedly) O(n^3), because of the internal calls of Object.assign() that needs to copy bigger and bigger objects from iteration to iteration.

This is what JavaScript does internally:

objects.reduce((lookup, object) => Object.assign(
            Object.assign(lookup, {}), 
            { [object.id]: object.name }
        ), 
    {}
);

Time taken:

    100 records: ~0.08ms
  1.000 records: ~1.45ms
 10.000 records: ~  18ms
100.000 records: ~2600ms
1 mill  records: ~breaks

Performance boost:

const lookup = objects.reduce((lookup, object) => {
    lookup[object.id] = object.name
    return lookup
}, {})

Time taken:

    100 records: ~0.045ms
  1.000 records: ~ 0.15ms
 10.000 records: ~  1.5ms
100.000 records: ~  3.2ms
1 mill  records: ~   20ms

Great performance improvement by avoiding Object.assign calls and new object creation in each iteration.

Folder aliases using webpack

In order to define a folder alias using webpack, all you have to do is write the following in webpack.config.js:

const path = require('path');

module.exports = {
  ...
  resolve: {
    alias: {
      '@': path.resolve('src'),
      images: path.resolve('assets/images'),
    },
  },
};

Now , if you want to import something from assets/images, all you need do to is

import image from 'images/[path]'

regardless of the current directory.

If the location of any of the folders defined with aliases ever changes, you no longer need to update all the imports - changing the path in webpack.config.js will take care of everything.

Conditionally loading packages based on environment.

Based on the environment of the project (production or development), there is a way to load packages conditionally. We may want to load packages conditionally to keep an easier to run development server, for instance.

In order to achieve this, we may use the following technique, if the used packages do not already feature a parameter to be used accordingly to the environment.

For instance we may provide an APP_ENVIRONMENT parameter in the .env file of the project. Based on the value of the parameter, we may load or not a package.

.env

APP_ENVIRONMENT=development

nuxt.config.js

let modules = {
  "@nuxtjs/axios",
}

if (process.env.APP_ENVIRONMENT !== "development")
  modules.push("@nuxtjs/component-cache")

Conditionally add properties to objects in JavaScript

This comes in handy when you want to completely omit a property from an object in case the value is undefined or null.

{prop1, prop2, ...(condition && prop3)}

The trick being here:

...(condition && prop3)

In case the condition evaluates to false, the property will be completely omitted from the object, otherwise, both the value and its corresponding key will show up in the end result.

Correctly grouping members within modules in JSDoc v3

JSDoc is great! It's documentation not so much. I needed a way to logically group together global methods and I was unsuccessful in finding a straightforward way in its official documentation.

Eventually, I've come across the following method, using @module and @member.

First, we define the module name, usually at the top of the file:

@module MyModule

We then proceed to annotate the target function with @memeber MyModule. One other important aspect is to also include @function along with the name of the function in order to appear in the module section.

/**
 * @member MyModule
 * @function myFunction
 ....
 */
 export const myFunction = () => ....