…if you’re using Babel.

The previous time I wrote a post about WebP image format, a modern image compression technology. I want to continue the topic about modern Web and today I’m going to tell something about new ECMAScript features and check if lodash is really necessary nowadays.

lodash

Why lodash became popular? The answer is really simple. Because it solved a lot of common tasks in JavaScript development. But let’s look closer at this library.

Advantages

Lodash has a lot of handy functions to work with JavaScript objects, arrays, and even functions. If you have to do something (array sorting, object mapping, function currying, etc), lodash will likely help you with that.

The next advantage is a null-safety (resistance to undefined). What’s that? Have you ever seen a TypeError: Cannot read property 'prop' of undefined? I bet you have. To avoid them you need to wrap your code into conditions:

let obj;
let result;
if (obj) {
    result = obj.prop;
}

But you can stop doing so with lodash because you can just write let result = _.get(obj, 'prop') and it will be an equivalent of a code above. This is one of the simplest cases when null-safety is handy.

Disadvantages

Today we’re living in a world where smartphones are beating desktops by usage stats. We should be prepared for users with very weak devices. Lodash has a performance impact. A code written in a pure JavaScript is much faster than its lodash equivalent. Moreover, lodash adds some extra kilobytes to your JavaScript bundles.

Another 3rd party library jQuery was like a plague. Do you remember the time when almost all websites were using jQuery? Developers used jQuery for everything, they learned how to program their websites with jQuery, not JavaScript. I’m afraid lodash is something like a next jQuery now.

The popularity of lodash became its weak side. Obviously, it is nice when every JavaScript developer knows the library. But if you’ll look on the NPM’s Most depended-upon packages list you’ll find that lodash is in the top of that. That means that lodash is used by the incredible amount of other packages. But do they actually need lodash? I definitely want to check it.

Investigations

There is a nice list of popular packages (thanks to npmjs.com) which depend on lodash. I wrote a simple script to clone this list. Finally, I got the 30 most popular packages that are using lodash, this should be enough for the research. Here they are:

Inquirer.js
ant-design
async
cheerio
elasticsearch-js
enzyme
eslint
eslint-plugin-flowtype
eslint-plugin-import
generator
grpc-node
grunt-contrib-watch
gulp-template
gulp-uglify
html-webpack-plugin
http-proxy-middleware
jshint
karma
knex
node-archiver
node-restify
react-dnd
react-native
react-redux
redux-form
sequelize
stylelint
webpack-bundle-analyzer
webpack-manifest-plugin
webpack-merge

I think you are already familiar with some of them. Actually, most of them will never be used on the client-side and should be installed as devDependencies. But anyway, all of them are using lodash for some reasons.

So what lodash functions are most popular in these libraries? I tried to fetch them in the following way:

grep -E "\s(_|lodash)\.\w+\(" -oh -r . | \
    sed -E "s/.*(lodash|_)\.([a-zA-Z]+).+/_.\2/" | \
    sort | uniq -c | sort -r | head -10

This shell command searches lodash functions and makes stats. That’s how I got a top 10 most used lodash functions in a top 30 most popular packages which are using lodash:

 123 _.map
 112 _.each
  98 _.get
  97 _.extend
  84 _.assign
  80 _.defaults
  80 _.clone
  74 _.template
  54 _.forEach
  49 _.bind

First column is an amount of corresponding lodash function usages. That’s look interesting, let’s check if all of them are so necessary.

The top 10 lodash functions

_.map

Collections mapping is a very common action in the JavaScript world. This lodash function can work with objects and arrays. Usually, we’re using it for mapping array of objects into another array of values, processed somehow. ECMAScript also has this for arrays. So we have a nearly easy way to write identical code using native methods:

const data = [
    {id: 1, name: 'Bob'},
    {id: 2, name: 'John'},
    {id: 3, name: 'Sarah'},
];
data.map(item => item.name);
// ['Bob', 'John', 'Sarah']

But what if data is an object. We can work with that in a very similar way:

const data = {
    1: {name: 'Bob'},
    2: {name: 'John'},
    3: {name: 'Sarah'},
};
Object.values(data).map(item => item.name);
// ['Bob', 'John', 'Sarah']

Remember I told you about null-safety? We can add it either.

const data = Math.random() >= 0.5 ? undefined : {
    1: {name: 'Bob'},
    2: {name: 'John'},
    3: {name: 'Sarah'},
};
Object.values(data || {}).map(item => item.name);
// ['Bob', 'John', 'Sarah'] or []

Of course you don’t need to think about data type you have with lodash, you can just always write _.map and it will work. But a native way also looks not bad.

_.each, _.forEach

_.each is just an alias for _.forEach.

This method iterates over arrays and objects. ECMAScript’s forEach function works only for arrays. If we want to migrate our code from lodash to native JavaScript, approaches would be nearly the same as we used for _.map.

But actually, there are some more differences between _.forEach and native forEach. Lodash version supports breaks, we can break the loop by returning false from its callback function. If you need to make a break in a native JavaScript, use a for...of statement

const data = Math.random() >= 0.5 ? undefined : {
    1: {name: 'Bob'},
    2: {name: 'John'},
    3: {name: 'Sarah'},
};
const dataArray = Object.values(data || {});
for (const user of dataArray) {
    if (user.name === 'John') {
        break;
    }
    console.log(user.name);
}
// 'Bob'

for...of usage is even better because you don’t need extra callback functions and your code would run in the same function block.

_.get

I believe this one should be on the first place by usage frequency, I don’t know why it isn’t so. This handiest lodash function allows accessing nested object properties safely. It is interesting fact, but ECMAScript already has an alternative for that, a TC39 optional chaining proposal. Let’s write some code using this new feature.

Optional chaining is on stage 1 and unfortunately it is not possible to run this code on any JavaScript engine, but we can use a Babel transpiler for that. To use it I created a simple sandbox project.

mkdir optional-chaining && cd optional-chaining
npm init -y
npm i --save-dev @babel/core @babel/cli @babel/plugin-proposal-optional-chaining
echo '{ "plugins": ["@babel/plugin-proposal-optional-chaining"] }' > .babelrc

And added some code into index.js

const data = [
    undefined,
    {id: 1, name: 'Bob'},
];
data.forEach(item => {
    console.log(item?.name);
});

Let’s run it

npx babel index.js | node
// undefined
// Bob

There was no TypeError: Cannot read property 'name' of undefined while accessing a property name of undefined. That’s how null-safety helps us in cases when we aren’t sure about data type we have.

Setting up Babel is a little complicated, but it allows to achieve null-safety in ECMAScript nowadays. In addition, if you’re already using Babel in your project, you can just add this plugin and be happy with this neat feature.

_.extend, _.assign, _.defaults

I put these methods together because they are very similar. All of them are used for merging objects in various ways. The difference between _.extend and _.assign is that _.extend enumerates own and prototype object properties. When we are talking about prototypes in JavaScript, we are usually talking about inheritance. You can read about JavaScript inheritance in this comprehensive article by Axel Rauschmayer. I believe that _.extend is an old-school inheritance-like method, and it would be better to use a modern class...extends statements today. Using extension of classes in JavaScript is an explicit way to show intents of your code. In practice, there is no drop-in replacement of _.extend method.

So what’s about _.assign and _.defaults. In the real world, their behavior is almost the same but they have a different arguments order. In fact, _.defaults has more details in its implementation, but usually, it doesn’t matter.

As I said before these group of methods are used for different ways of objects merging. ECMAScript already has a native way for basic objects merging. It is possible with spread operator, this operator is already a part of ECMAScript 2015 standard. Let’s imagine that we have some settings we want to use by default and some settings to override these defaults:

function applyDefaultSettings(settings) {
    const defaultSettings = {flag: false, option: 2, value: 'on'};
    return {
        ...defaultSettings,
        ...settings,
    };
}
const settings = {option: 5};
const preparedSettings = applyDefaultSettings(settings);
console.log(preparedSettings);
// {flag: false, option: 5, value: 'on'}

Using spreads in this way is a native alternative for both of _.assign and _.defaults methods.

_.clone

Lodash clone creates a shallow copy of various data types. But what does shallow mean? It means that nested non-scalar properties of a value would not be copied actually. So if you are trying to make a copy of a value which is an object/array and it has nested objects/arrays, those nested values would not be copied. Let’s check it.

const bob = {name: 'Bob', has: ['cat', 'dog', 'house']};
const bobsClone = _.clone(bob);
console.log(bob.has === bobsClone.has);
// true
console.log(bob === bobsClone);
// false

bob.has and bobsClone.has are just references to the same array but bob and bobsClone are different objects. So bobsClone is a shallow copy of bob.

What’s about ECMAScript? Does it have an alternative for the shallow copy? Yes, it has. We can do this with the same spread operator, which we used in previous section. The equivalent of example above in native JavaScript:

const bob = {name: 'Bob', has: ['cat', 'dog', 'house']};
const bobsClone = {...bob};
console.log(bob.has === bobsClone.has);
// true
console.log(bob === bobsClone);
// false

Pretty simple isn’t it? Spread operator works for arrays as well. I beleive we don’t need a lodash _.clone for shallow copying.

_.template

Well this one doesn’t have alternatives in a native JavaScript because it’s a part of lodash / underscore template engine. Let’s just skip it.

_.bind

Binding context to function is a common practice in JavaScript. It even can bind function arguments, but let’s look at basics. So what does binding mean? Let’s check this example:

const utils = {
    getName() {
        return this.name;
    }
};

Here is the utils object with the getName method. This method works in some context (this) and fetches a name field from the context. By default, the context will be the utils object itself.

If we will run this method we would get undefined because the utils context doesn’t have a name property.

But there is a way how to substitute a context for function with bind. This function creates a new function which always runs in a specified context.

Well, I just described what’s the function binding in JavaScript but didn’t tell anything about how to use lodash _.bind and native bind. Actually, usage of native and lodash binding functions are really similar:

const context = {name: 'Sarah'};

const nativeBoundGetName = utils.getName.bind(context);
nativeBoundGetName();
// Sarah

const lodashBoundGetName = _.bind(utils.getName, context);
lodashBoundGetName();
// Sarah

So we have a good alternative for lodash _.bind method.

Conclusions

Whew, this post turned out quite large. But I believe this little research was important because we found that (probably) lodash is not necessary nowadays. We still need Babel for this sometimes, but it is better to use Babel instead of 3rd party libraries because this is the future of JavaScript language. When new features become a part of ECMAScript standard, they are starting to work without Babel transpiler and JavaScript engines would probably get some native optimizations for them.

Also if you’re developing client-side applications for browsers lodash adds extra 24 KB to your JS bundles. Of course, you can add some changes into your code to enable tree-shaking, but if you are using another libraries which depend on lodash you will meet some issues.

That’s all guys, I think it’s time to end this post.

lodash/fp

In the end, I just wanted to say a few words about lodash/fp. You don’t need it either. If you’ve ever used lodash/fp you probably noticed how bad lodash/fp documentation is. But if you want to use functional programming approaches you would definitely need a library for that. I can recommend using Ramda, it has good documentation and it is smaller than lodash. Yet.

See also

  1. The script I used to download popular NPM packages.
  2. Nice recent article The Cost of JavaScript by Addy Osmani. If you didn’t read it you should do it soon. It contains very important stats about the performance of modern devices and how JS and CSS bundle sizes affect the average user.
  3. A really nice project I found while writing this blog post. Its name You don’t (may not) need Lodash/Underscore. There are many code snippets with native alternatives of some lodash functions.