Disclaimer: This is not a dig at either of the fantastic underscore or lodash libraries, nor any other utility library out there. It is not a call to stop using these or any other library. Nor is a pitch for some replacement. Rather this is a suggestion to stop and think. Is using a utility library improving the end user experience on a given project, or are we just falling into habit from previous projects?
Every added dependency has a cost, especially when it comes to browser-side JavaScript. Much of the time this cost is completely justified — even required — but sometimes we just tend to reach for what we are familiar with and not stop to consider the problem we are addressing. The same thing happened with jQuery. It became so ubiquitous that even when the web platform offered native solutions to the vast majority of common use cases, it was still quite a while before there was a major push for developers to ask themselves if jQuery still made sense for their projects, or if it was overkill now.
Just like jQuery, I don’t see utility libraries like underscore or lodash going away anytime soon, nor do I think they should. But I do think it’s time to look at what we are building and where sensible native solutions exists, favor those.
Also just like jQuery, advocates for underscore, lodash, etc., will argue points about efficiency, performance, cross-browser compatibility, etc. These points are all valid, and if the native solutions don’t provide everything you need, or polyfills get too unwieldy, then by all means, use the library. The point of this article is to get us to think before reaching for a large library. Underscore/lodash is just the latest straw-man in this argument. The mantra is simplify. If using the native versions for these problems isn’t creating a simpler solution, then it’s not the right choice. That door swings both ways.
Let us begin
For brevity and readability, I will just be referring to lodash. Lodash has a very large collection of utilities and addressing each one in this post will quickly make this post intimidating for you to read and me to write. We will start the series by looking first at Lodash’s Array functions. This series will break down lodash Array functions into four categories:
-
Native Twins
These are library functions that have direct native solutions. You should have a pretty good reason if you’re to not going to use the native version for these.
-
Trivial Native Alternatives
Consider using these native solutions if this is the extent to which you would use a utility library like underscore or lodash. May need light code commenting to elucidate what is happening.
-
Moderately Complex Native Solutions
For the occasional one off problem. A handful of these per project and you may want to consider bringing in the library version as a module for these.
-
Maybe you do need that utility library after all
Don’t bother rewriting these. A native solution doesn’t exist (yet) and the solutions are sufficiently complex that you are likely to miss some edge cases that will bite you later.
This post will only cover the functions with native twins. Next in the series we will cover functions that are trivial to implement but don’t have a direct native twin.
Native Twins
One may ask one’s self why a library would implement a function that does exactly the same thing as native functions. Well there are valid reasons, but it does not follow that because someone (or even many someones) had a need for these functions, that your project has the same needs. In fact, I might argue that most of the time, you don’t.
For the most part, these functions are used exactly the same way the library versions are. Notes are made inline where there may be a caveat.
_.concat
_.concat
concatenates any number of arrays or items onto a source array, producing a new array with all the values.
_.concat([1,2,3], 4, [5,6,7,8], [[9]])
// [1,2,3,4,5,6,7,8,[9]]
[].concat
works exactly the same way, only it is a property of the source array itself.
[1,2,3].concat(4, [5,6,7,8], [[9]]);
// [1,2,3,4,5,6,7,8,[9]]
_.fill
_.fill
will take an array and replace all values in with the supplied fill value.
_.fill([1,2,3,4], ‘x’) // [‘x’, ‘x’,‘x’,‘x’]
_.fill(Array(3), 9) // [9, 9, 9]
_.fill([1,2,3,4], '*', 1, 3) // [1, '*', '*', 4]
[].fill
does the same thing.
[1,2,3,4].fill(‘x’) // [‘x’, ‘x’,‘x’,‘x’]
(Array(3)).fill(9) // [9, 9, 9]
[1,2,3,4].fill('*', 1, 3) // [1, '*', '*', 4]
_.findIndex
_.findIndex
will return the index of the first occurrence of an item that passes the predicate.
_.findIndex([1,2,3,4,5], (item) => item % 2 === 0) // 1
Unsurprisingly, the language also has a [].findIndex
which does the same thing.
[1,2,3,4,5].findIndex((item) => item % 2 === 0) // 1
_.indexOf
_.indexOf
is similar to _.findIndex
, but takes a search element rather than a predicate. It may also take a searchfrom
parameter that begins iterating at the provided index.
_.indexOf([1, 2, 1, 2], 2);
// 1
// Search from the `fromIndex`.
_.indexOf([1, 2, 1, 2], 2, 2);
// 3
[].indexOf
has the same signature and functions the same way.
[1, 2, 1, 2].indexOf(2);
// 1
// Search from the `fromIndex`.
[1, 2, 1, 2].indexOf(2, 2);
// 3
_.join
_.join
turns an array into a string separating each item with the value of the separator
parameter
_.join([1,2,3,4,5], ', ') // '1, 2, 3, 4, 5'
[].join
is identical
[1,2,3,4,5].join(', ') // '1, 2, 3, 4, 5'
_.reverse
_.reverse
reverses the order of an array in place. Meaning it mutates the array
_.reverse([1,2,3,4,5]) // [5,4,3,2,1]
Same for native [].reverse
, including it’s mutative property
[1,2,3,4,5].reverse() // [5,4,3,2,1]
_.slice
_.slice
will create and return a sub array from a source array starting at startIndex
, defaulting to 0
, up to but not including endIndex
which defaults to arr.length
.
_.slice([1,2,3,4,5]) // [1,2,3,4,5]
_.slice([1,2,3,4,5], 1) // [2,3,4,5]
_.slice([1,2,3,4,5], -2) // [4,5]
_.slice([1,2,3,4,5], 1, 3) // [2,3]
_.slice([1,2,3,4,5], -3, -1) // [4,5]
[].slice
works the same way.
[1,2,3,4,5].slice() // [1,2,3,4,5]
[1,2,3,4,5].slice(1) // [2,3,4,5]
[1,2,3,4,5].slice(-2) // [4,5]
[1,2,3,4,5].slice(1, 3) // [2,3]
[1,2,3,4,5].slice(-3, -1) // [4,5]
In Conclusion
Well done everyone! We have successfully examined a number of utility functions that have native implementations in javascript. Whether you choose to use the native versions or a utility library to accomplish these tasks is completely up to you. I hope now at least you have just a bit more information as to what you are able to accomplish with just the platform.
I would love to hear comments, feedback and polite, professional discussions in the comments. Next time we will look at some more common utility functions that don’t have a direct native counterpart, but are nonetheless trivial to do natively. Don’t miss it.