DEV Community

Cover image for So you think you need _Lodash?
marco cabrera
marco cabrera

Posted on

So you think you need _Lodash?

Intro

Recently, I got caught up in a discussion about Lodash and whether we really need to lean on third-party libraries as much as we do in the JavaScript world. It's clear we love our shortcuts and helpers, but it's also incredible to see how JavaScript itself has grown, turning from a basic scripting language into something much more powerful, complete with its own advanced features like array and map methods. This article is about putting Lodash side by side with vanilla JavaScript to see how they compare today. Feel free to follow along on a browser console for the Vanilla JS potion of the blog.

Deep Copy

Let's start with the concept of deep copying in JavaScript. It's a common necessity across programming tasks.

Lodash

Lodash provides a _.cloneDeep method to perform a deep copy:

const _ = require('lodash');

let obj = { a: 1, b: { c: 2 } };
let deepCopiedObj = _.cloneDeep(obj);

console.log(deepCopiedObj); // => { a: 1, b: { c: 2 } }
Enter fullscreen mode Exit fullscreen mode

Vanilla Javascript

Vanilla JavaScript offers a couple of ways to achieve deep copying, each with its own considerations. Here's a look at two prevalent methods.

With modern JavaScript, you can achieve a deep copy using the structuredClone method.

let obj = { a: 1, b: { c: 2 } };
let deepCopiedObj = structuredClone(obj);

console.log(deepCopiedObj); // => { a: 1, b: { c: 2 } }
Enter fullscreen mode Exit fullscreen mode

Vanilla JavaScript

Another way is to use the JSON.parse and JSON.stringify methods.

let obj = { a: 1, b: { c: 2 } };
let deepCopiedObj = JSON.parse(JSON.stringify(obj));

console.log(deepCopiedObj); // => { a: 1, b: { c: 2 } }
Enter fullscreen mode Exit fullscreen mode

Note: This method works well for objects containing only primitive values, arrays, and plain objects but might not be suitable for objects with methods, circular references, or special types like Date, Set, Map, etc. I honestly hate seeing this, but I know it's prevalent in many code bases i and can attest to it working, so I have included it in this article.

Difference

Finding the difference between two arrays is a common task, where we identify elements present in one array but not in another.

Lodash


_.difference([2, 1], [2, 3]); // => [1]
Enter fullscreen mode Exit fullscreen mode

Vanilla JavaScript

let firstArr = [1,2,3];
let secondArr = [2,4];
let difference = firstArr.filter(x => !secondArr.includes(x));
console.log(difference); // => [1, 3]
Enter fullscreen mode Exit fullscreen mode

DifferenceBy

DifferenceBy introduces a twist to finding array differences by allowing comparisons based on a transformation function.

Lodash


_.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor); // => [1.2]
Enter fullscreen mode Exit fullscreen mode

Vanilla JavaScript

let firstArr = [1,2,3, 4.2];
let secondArr = [2,4];

// Various approaches but they get more funtional
let difference = firstArr.filter(x => !secondArr.includes(Math.floor(x)));
console.log("difference:" + difference); // => [1, 3]

const diffBy = element => Math.floor(element);
let differenceWithFunc = firstArr.filter(element => !secondArr.includes(diffBy(element)));
console.log("differenceWithFunc:" + differenceWithFunc); // => [1, 3]

const transform = element => Math.floor(element);
const notIncluded = (element, transformFunc, secondArray) => !secondArray.includes(transformFunc(element));
let differenceEncapsulationVersion = firstArr.filter(element => notIncluded(element, transform, secondArr));

console.log("differenceEncapsulationVersion:" + differenceEncapsulationVersion); // => [1, 3]

Enter fullscreen mode Exit fullscreen mode

Union

Union operations merge multiple arrays into a single array with unique elements. This common functionality showcases another aspect of array manipulation.

Lodash

_.union([2], [1, 2]); // => [2, 1]
Enter fullscreen mode Exit fullscreen mode

Vanilla JavaScript

Using ES6's Set and spread operator:

let firstArr = [2];
let secondArr = [1,2];
let thirdArr = [2,3,4];
let union = new Set([...firstArr, ...secondArr, ...thirdArr]);
console.log([...union]); // => [2, 1, 3, 4]
Enter fullscreen mode Exit fullscreen mode

Zip

Zipping arrays is a powerful method to combine arrays element-wise.

Lodash

_.zip(['a', 'b'], [1, 2], [true, false]); // => [['a', 1, true], ['b', 2, false]]
Enter fullscreen mode Exit fullscreen mode

Vanilla JavaScript

Using Array.from, we can zip arrays of varying lengths:

function zip(...arrays) {
    const maxLength = Math.max(...arrays.map(arr => arr.length));
    return Array.from({ length: maxLength }, (_, index) => arrays.map(array => array[index]));
}

let abc = ['a', 'b', 'c'];
let num = [1, 2, 3];
let bool = [true, false, true];

let zipped = zip(abc, num, bool);
console.log(zipped);
Enter fullscreen mode Exit fullscreen mode

A more functional approach:

const zip = (...arrays) => arrays[0].map((_, i) => arrays.map(array => array[i]));

let abc = ['a', 'b', 'c'];
let num = [1, 2, 3];
let bool = [true, false, true];

let zipped = zip(abc, num, bool);
console.log(zipped); // => [['a', 1, true], ['b', 2, false], ['c', 3, true]]
Enter fullscreen mode Exit fullscreen mode

Currying

Currying transforms a function, so it can be invoked piecewise, with each call returning a new function that accepts the next argument.

Lodash

var abc = function(a, b, c) {
    return [a, b, c];
};

var curried = _.curry(abc);

curried(1)(2)(3); // => [1, 2, 3]
curried(1, 2)(3); // => [1, 2, 3]
curried(1, 2, 3); // => [1, 2, 3]
curried(1)(_, 3)(2); // => [1, 3, 2]
Enter fullscreen mode Exit fullscreen mode

Vanilla JavaScript

implementing currying with support for placeholders:

const _ = Symbol('_');

function curry(fn) {
    return function currier(...args) {
        const isComplete = args.length >= fn.length && !args.slice(0, fn.length).includes(_);
        if (isComplete) {
            return fn.apply(this, args);
        } else {
            return function(...newArgs) {
                const combinedArgs = args.reduce((acc, arg) => {
                    if (arg === _ && newArgs.length) acc.push(newArgs.shift());
                    else acc.push(arg);
                    return acc;
                }, []);
                return currier(...combinedArgs, ...newArgs);
            };
        }
    };
}

const abc = (a, b, c) => [a, b, c];
const curried = curry(abc);

console.log(curried(1)(2)(3)); // => [1, 2, 3]
let unfinishedCurry = curried(1,2);
console.log(unfinishedCurry(3)); // => [1, 2, 3]
console.log(curried(1, 2)(3)); // => [1, 2, 3]
console.log(curried(1)(_, 3)(2)); // => [1, 3, 2]
console.log(curried(1, _, _)(2)(3)); // => [1, 2, 3]
Enter fullscreen mode Exit fullscreen mode

Conclusion

There's an argument to be made for using only specific parts of Lodash to minimize the footprint, yet the essence of my argument remains: As developers, our aim should be to deeply understand the functions we employ. Relying heavily on abstractions can, if not careful, erode our foundational skills. This isn't to undermine Lodash's value; on the contrary, I hope this article highlights its excellence. Lodash has played a pivotal role by providing robust functionalities during a time when such features were lacking in native browser environments.

Currently, I find myself not relying on Lodash as much, though I remain open to the possibility that it might offer indispensable solutions to complex problems in the future. My journey through this exploration was not just to question commonly held beliefs but to deepen my own grasp of JavaScript and to hone my skills. This reflection on JavaScript's growth celebrates Lodash's contribution to easing software development and acknowledges the language's continuous, albeit occasionally contentious, evolution. The path JavaScript is on is filled with promise and excitement for what's yet to come.

Top comments (0)