JavaScript
Article
By Dan Prince

10 Lodash Features You Can Replace with ES6

By Dan Prince

This article was peer reviewed by Mark Brown. Thanks to all of SitePoint’s peer reviewers for making SitePoint content the best it can be!

Lodash is the most depended on npm package right now, but if you’re using ES6, you might not actually need it. In this article, we’re going to look at using native collection methods with arrow functions and other new ES6 features to help us cut corners around many popular use cases.

1. Map, Filter, Reduce

These collection methods make transforming data a breeze and with near universal support, we can pair them with arrow functions to help us write terse alternatives to the implementations offered by Lodash.

_.map([1, 2, 3], function(n) { return n * 3; });
// [3, 6, 9]
_.reduce([1, 2, 3], function(total, n) { return total + n; }, 0);
// 6
_.filter([1, 2, 3], function(n) { return n <= 2; });
// [1, 2]

// becomes

[1, 2, 3].map(n => n * 3);
[1, 2, 3].reduce((total, n) => total + n);
[1, 2, 3].filter(n => n <= 2);

It doesn’t stop here either, if we’re using an ES6 polyfill, we can also use find, some, every and reduceRight too.

2. Head & Tail

Destructuring syntax allows us to get the head and tail of a list without utility functions.

_.head([1, 2, 3]);
// 1
_.tail([1, 2, 3]);
// [2, 3]

// becomes

const [head, ...tail] = [1, 2, 3];

It’s also possible to get the initial elements and the last element in a similar way.

_.initial([1, 2, 3]);
// -> [1, 2]
_.last([1, 2, 3]);
// 3

// becomes

const [last, ...initial] = [1, 2, 3].reverse();

If you find it annoying that reverse mutates the data structure, then you can use the spread operator to clone the array before calling reverse.

const xs = [1, 2, 3];
const [last, ...initial] = [...xs].reverse();

3. Rest & Spread

The rest and spread functions allow us to define and invoke functions that accept a variable number of arguments. ES6 introduced dedicated syntaxes for both of these operations.

var say = _.rest(function(what, names) {
  var last = _.last(names);
  var initial = _.initial(names);
  var finalSeparator = (_.size(names) > 1 ? ', & ' : '');
  return what + ' ' + initial.join(', ') +
    finalSeparator + _.last(names);
});

say('hello', 'fred', 'barney', 'pebbles');
// "hello fred, barney, & pebbles"

// becomes

const say = (what, ...names) => {
  const [last, ...initial] = names.reverse();
  const finalSeparator = (names.length > 1 ? ', &' : '');
  return `${what} ${initial.join(', ')} ${finalSeparator} ${last}`;
};

say('hello', 'fred', 'barney', 'pebbles');
// "hello fred, barney, & pebbles"

4. Curry

Without a higher level language such as TypeScript or Flow, we can’t give our functions type signatures which makes currying quite difficult. When we receive curried functions it’s hard to know how many arguments have already been supplied and which we will need to provide next. With arrow functions we can define curried functions explicitly, making them easier to understand for other programmers.

function add(a, b) {
  return a + b;
}
var curriedAdd = _.curry(add);
var add2 = curriedAdd(2);
add2(1);
// 3

// becomes

const add = a => b => a + b;
const add2 = add(2);
add2(1);
// 3

These explicitly curried arrow functions are particularly important for debugging.

var lodashAdd = _.curry(function(a, b) {
  return a + b;
});
var add3 = lodashAdd(3);
console.log(add3.length)
// 0
console.log(add3);
//function wrapper() {
//  var length = arguments.length,
//  args = Array(length),
//  index = length;
//
//  while (index--) {
//    args[index] = arguments[index];
//  }…

// becomes

const es6Add = a => b => a + b;
const add3 = es6Add(3);
console.log(add3.length);
// 1
console.log(add3);
// function b => a + b

If we’re using a functional library like lodash/fp or ramda then we can also use arrows to remove the need for the auto-curry style.

_.map(_.prop('name'))(people);

// becomes

people.map(person => person.name);

5. Partial

Like with currying, we can use arrow functions to make partial application easy and explicit.

var greet = function(greeting, name) {
  return greeting + ' ' + name;
};

var sayHelloTo = _.partial(greet, 'hello');
sayHelloTo('fred');
// "hello fred"

// becomes

const sayHelloTo = name => greet('hello', name);
sayHelloTo('fred');
// "hello fred"

It’s also possible to use rest parameters with the spread operator to partially apply variadic functions.

const sayHelloTo = (name, ...args) => greet('hello', name, ...args);
sayHelloTo('fred', 1, 2, 3);
// "hello fred"

6. Operators

Lodash comes with a number of functions that reimplement syntactical operators as functions, so that they can be passed to collection methods.

In most cases, arrow functions make them simple and short enough that we can define them inline instead.

_.eq(3, 3);
// true
_.add(10, 1);
// 11
_.map([1, 2, 3], function(n) {
  return _.multiply(n, 10);
});
// [10, 20, 30]
_.reduce([1, 2, 3], _.add);
// 6

// becomes

3 === 3
10 + 1
[1, 2, 3].map(n => n * 10);
[1, 2, 3].reduce((total, n) => total + n);

7. Paths

Many of Lodash’s functions take paths as strings or arrays. We can use arrow functions to create more reusable paths instead.

var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };

_.at(object, ['a[0].b.c', 'a[1]']);
// [3, 4]
_.at(['a', 'b', 'c'], 0, 2);
// ['a', 'c']

// becomes

[
  obj => obj.a[0].b.c,
  obj => obj.a[1]
].map(path => path(object));

[
  arr => arr[0],
  arr => arr[2]
].map(path => path(['a', 'b', 'c']));

Because these paths are “just functions”, we can compose them too.

const getFirstPerson = people => people[0];
const getPostCode = person => person.address.postcode;
const getFirstPostCode = people => getPostCode(getFirstPerson(people));

We can even make higher order paths that accept parameters.

const getFirstNPeople = n => people => people.slice(0, n);

const getFirst5People = getFirstNPeople(5);
const getFirst5PostCodes = people => getFirst5People(people).map(getPostCode);

8. Pick

The pick utility allows us to select the properties we want from a target object. We can achieve the same results using destructuring and shorthand object literals.

var object = { 'a': 1, 'b': '2', 'c': 3 };

return _.pick(object, ['a', 'c']);
// { a: 1, c: 3 }

// becomes

const { a, c } = { a: 1, b: 2, c: 3 };

return { a, c };

9. Constant, Identity, Noop

Lodash provides some utilities for creating simple functions with a specific behaviour.

_.constant({ 'a': 1 })();
// { a: 1 }
_.identity({ user: 'fred' });
// { user: 'fred' }
_.noop();
// undefined

We can define all of these functions inline using arrows.

const constant = x => () => x;
const identity = x => x;
const noop = () => undefined;

Or we could rewrite the example above as:

(() => ({ a: 1 }))();
// { a: 1 }
(x => x)({ user: 'fred' });
// { user: 'fred' }
(() => undefined)();
// undefined

10. Chaining & Flow

Lodash provides some functions for helping us write chained statements. In many cases the built-in collection methods return an array instance that can be directly chained, but in some cases where the method mutates the collection, this isn’t possible.

However, we can define the same transformations as an array of arrow functions.

_([1, 2, 3])
 .tap(function(array) {
   // Mutate input array.
   array.pop();
 })
 .reverse()
 .value();
// [2, 1]

// becomes

const pipeline = [
  array => { array.pop(); return array; },
  array => array.reverse()
];

pipeline.reduce((xs, f) => f(xs), [1, 2, 3]);

This way, we don’t even have to think about the difference between tap and thru. Wrapping this reduction in a utility function makes a great general purpose tool.

const pipe = functions => data => {
  return functions.reduce(
    (value, func) => func(value),
    data
  );
};

const pipeline = pipe([
  x => x * 2,
  x => x / 3,
  x => x > 5,
  b => !b
]);

pipeline(5);
// true
pipeline(20);
// false

Conclusion

Lodash is still a great library and this article only offers a fresh perspective on how the evolved version of JavaScript is allowing us to solve some problems in situations where we would have previously relied on utility modules.

Don’t disregard it, but instead—next time you reach for an abstraction—think about whether a simple function would do instead!

  • Yep! That’s the big win from the Lodash deep path support. It fills the gap of CoffeeScript’s existential operator (a?.b?.c)

  • Excellent article! Thanks for this. This is probably just microperformance most of the time, but I’d love to see some metrics on es6 native vs lodash.

  • Bogdan Zaharia

    The `pick` example is nice, unfortunately it is not generic, not until JavaScript will have .map/.reduce/etc on objects as well.

    • Dan Prince

      I might be missing something, but I don’t quite see how having collection methods on objects affects the `pick` function?

      • Bogdan Zaharia

        Yes, my phrasing was pretty ambiguous. I was thinking of the `.filter` method on objects (which would still return an object, maybe a better name would be `.filterEntries` or something like that). So pick would be `pick = (obj, keys) => o.filter((value, key) => keys.includes(key))`

        • Dan Prince

          Ah, I see. You’re talking about implementing pick. Personally, I much prefer to cut out the need for extra code altogether and use destructuring assignment to achieve the same thing.

  • `map`, `filter`, `reduce`, `some`, etc. are ES5 features, not ES6. An ES6 polyfill won’t help you with those…

    • Dan Prince

      The focus is supposed to be on the arrow functions and I don’t think I’ve implied that an ES6 polyfill will help with them either. “find, some, every and reduceRight” are what’s mentioned.

  • Dan Prince

    In general, Lodash is more generic and will keep you a bit safer than vanilla JavaScript. The tradeoff comes with the ability to write and compose more natural selectors and write them as code, rather than as strings

  • Ainthe Kitchen

    Curry rewrite misses the point of keeping add original signature add(a,b)

    • Dan Prince

      The point I’m making, is that we should care more about keeping the two signatures distinct. `a => b => a + b` is a very different function to `(a, b) => a + b`. I’m suggesting that if you need the curried argument format, you define it explicitly.

      When you can define the function explicitly, there’s no room for ambiguity, whereas a standard JS curry implementation will introduce a layer of abstraction which makes the function hard to debug and impossible to infer with regards to arity and already-passed arguments.

  • Dan Prince

    Definitely interesting to get your opinions here! More than anything else, the article is aimed at getting people thinking in terms of ES6, rather than to provide an exhaustive list of 100% compatible alternatives.

    > Map, Filter, Reduce:
    I mainly included this section because I still know programmers who use lodash for these methods because they think browser support hasn’t caught up yet.

    > Curry
    What I’m suggesting is that implicit currying is not necessarily a good thing in JavaScript. The idea is good but I think the actuality creates more problems than it solves. However, in cases where you need a function returning another function, explicit currying keeps your curried function relatively debuggable and arrows allow you to play with the argument ordering and grouping, almost as though you were writing a type signature.

    > Operators
    I hear what you’re saying about the operators being designed for composition and I tried to make that clear with the last couple of examples in that section. However, I think arrow functions are terse enough, that there’s no reason not to just define them inline instead: `_.add` vs `(a, b) => a + b`.

    > Chain + Flow
    Lazy evaluation is great, but even when it’s really important, just wrapping stuff in a thunk will get you the same effect. There’s no denying that shortcut fusion is cool too, but I’m not sure that it’s worth the complexity offset between an array of functions and the fluent chain, tap, thru API. For what it’s worth, I think those kinds of optimizations are best at an engine level when shortcuts can be inferred from simple or idiomatic usage of regular JavaScript constructs. Again, when performance is really important, doing some manual inlining to create composed map/filters is a perfectly good solution for most people.

    I guess the underlying theme here is that the efficiency of the syntax from the most recent ES proposal gives programmers a chance to err on the side of simplicity, rather than abstraction. Fewer concepts to learn and more natural edge cases to think about. To re-iterate, I don’t think anyone should use this is a justification for approaching lodash with negativity, but it is definitely important that progressive programmers are using the evolved version of the language to find alternative ways to solve problems. If this article encourages just one other person to do that, then I’ll be happy!

  • The curried/wrapped function toString result for debugging is a good call. I’ll make Lodash provide more meaningful toString representations.

  • This article is interesting, as it shows some interesting ways to do things… :-)
    That said, it won’t necessarily convince me to drop Lodash in these cases.
    In my AngularjS applications, I prefer to use Lodash functions to Angular ones (eg. angular.isDefined vs. _.isDefined, angular.copy vs. _.clone, etc.) at least by consistency, and because Lodash is generally more robust (in face of undefined values, etc.) and often faster.
    Idem, I bet _.map is faster than Array.map.

    Beside, I find some of your ES6 examples to be somehow less readable than Lodash ones. That’s a question of taste and habits, I suppose. I generally prefer “verbose” languages (more readable for the literature loving me) than “symbol oriented” languages (more pleasing for math buffs, I suppose).

    All this doesn’t reduce the interest of your article: having choice and alternative is good, and sometime getting Lodash to do 3 maps & filters can be overkill for simple scripts… :-D

  • LegoMushroom

  • I thought I’d see Object.assign() for _.assign()

  • Gunar C. Gessner

    Just because you can doesn’t mean that you should :)

    A somewhat debatable alternative to noop is `Function.prototype` (:

  • Dan Prince

    Until then, there’s always LiveScript!

    • bfredit

      I… think I’ll stick to lodash

  • LPGhatguy

    TypeScript is getting a safe navigation operator like CoffeeScript’s — I think it’s landing in 2.0

  • Same issue. If one of the props in the path are missing it errors out.

    • Растерянный странник

      shame on me
      I was sure it had to work that way

  • James

    I hope developers don’t fall for such click-bait articles, and get thinking “in terms of ES6” while discarding their perfectly valid, more reliable, more readable, tested lodash code. Even newer code does not need to be written with a conscious effort to avoid lodash – the benefits are too many to give up, especially for an utterly shoddy, infantile language-spec that doesn’t even work in browsers yet, and requires a build step.

    • Dan Prince

      I’m going to assume this is just a troll, but for the sake of more impressionable developers who may misguidedly think you know what you’re talking about, here are some responses.

      At no point does this article suggest that you should “discard” lodash. Infact, I even went as far as to say “Don’t disregard it”! This isn’t calling on anyone to make a conscious effort to avoid lodash.

      I’m not quite sure why you think ES6 is less readable, as the overwhelming community opinion seems to be that it has vastly improved lots of the language, however, it’s completely subjective, so I won’t thrash it out.

      Whilst you’re throwing around unsubstantiated terms like “utterly shoddy” and “infantile”, it might be helpful to remind you that the most recent versions of Node and all major browsers have the spec fully implemented (sans, a way to load modules). There’s nothing in this article that doesn’t work in my browser’s console already.

      Modern JavaScript applications need build steps. If you’d like to point out some non-trivial apps that are written and served up as script tags, then I’ll be impressed. Until then, everyone is using webpack, browserify or at least uglify. Augmenting them with a babel transform is trivial.

      No one is going to force you to use ES6 and you obviously don’t like it, but that’s ok. Vendors are committed to not breaking the web, which means you’ll be able to stay happy in ES5 land for as long as you like. Just try not to let your small-mindedness dissuade enthusiastic developers from exploring alternative ways of solving problems with the current version of the language.

  • Alex

    I believe your “Head & Tail” example will need an additional reverse on the initial if you want to duplicate the lodash results.


    _.initial([1,2,3]); // -> [1,2]
    const [last, ...initial] = [1, 2, 3].reverse(); // last = 3, initial = [2,1]
    initial.reverse(); // -> [1,2]

  • no, no, curry and partial are bad here, those are not equivalent. You are writing manually the curried version while the lodash does it for you. In JS curry is practically obsolete since the “call function” operator is “(…)” while in haskell is ” ” (and all functions are curried by default), let’s avoid ()()() patterns. About partial application, that is not partial application at all, what you wrote is a closure. Partial application would be ((x, y) => x + y)(5) but that doesn’t work in JS, the right way to achieve partial application is: ((x, y) => x + y).bind(null,5). About the noop and similar, well, you are writing your own function declaration while lodash already includes it (you can write this also in ES5 or other :) )

    • Dan Prince

      The point I’m making is that manually writing the curried signature makes much more sense than letting lodash convert it for you. That way you end up with a function whose semantics are clearer, is easier to debug and will perform better. Currying is not obsolete in JavaScript, but _most_ of the time there’s not a good reason to use it.

      Same reasoning goes for the dismissal of using higher order functions instead of strict partial application. If you use `.bind` you’re going to incur the penalty that comes with all of those context modifying Function.prototype methods _and_ the return value is going to be a meaningless [native code] function.

  • Great post.. Thanks

  • Faiwer

    Please, do not use `[last] = arr.reverse()`. `_.last` does only `arr[arr.length – 1]`, but `.reverse` makes new array with reversed positions. Very expensive.

    • Faiwer

      OMG, I read next paragraph. “const [last, …initial] = […xs].reverse()” – WAT? It’s kidding? Do you make new array and then reverse it only for getting last its item?

  • With just approved list of new proposals that will become part of a ECMAScript 2017 two new methods are worth adding to this list…

    https://pawelgrzybek.com/whats-new-in-ecmascript-2017/#objectvalues-and-objectentries

  • Zed

    This might be useful for a better understand of the examples while reading this article

    http://babeljs.io/repl/

Recommended
Sponsors
Get the latest in JavaScript, once a week, for free.