Simplify your JavaScript with Underscore.js

by Gennie Harris

Underscore.js, as described on their website, is "a utility-belt library for JavaScript that provides a lot of the functional programming support that you would expect in Prototype.js (or Ruby), but without extending any of the built-in JavaScript objects."

Simply put, this means that you can drastically transform your arrays, objects, and functions with only a line or three of code, making underscore arguably the single most useful JavaScript library ever written.

What it gives you

Let's say, for example, that you're doing some work on your Joss Whedon fan site, for which you have some data structured along these lines:

var jossWhedon = {
  age: 49,
  occupations: ["writer", "director", "producer", "composer", "actor"],
  shows: [
    {
      title: "Dollhouse",
      femaleLead: true,
      characters: [
        { name: "Echo", role: "doll" },
        { name: "Topher", role: "mad scientist" }
      ]
    },
    {
      title: "Dr. Horrible's Sing-Along Blog",
      characters: [
        { name: "Billy", role: "mad scientist" },
        { name: "Penny", role: "love interest" }
      ]
    },
    {
      title: "Buffy the Vampire Slayer",
      femaleLead: true,
      characters: [
        { name: "Buffy", role: "slayer" },
        { name: "Angel", role: "love interest" }
      ]
    },
    {
      title: "Firefly",
      characters: [
        { name: "Mal", role: "captain" },
        { name: "Kaylee", role: "mechanic" }
      ]
    }
  ]
}

How can you easily manipulate this data to sort and display it in the various ways necessary to run a successful site? Underscore provides over 80 functions, which do everything from basic mapping of arrays (map, of course) to creating "flexibly-numbered lists of integers" (range), to inverting the keys and values of an object (invert). Though you may find edge cases in which dozens of these become useful, there are a few that you'll find invaluable time and time again.

Pluck

One of the most convenient of Underscore's functions, pluck creates an array of property values. This is excellent for displaying or manipulating subsets of data. Want to see a human-readable list of the shows that Joss Whedon’s worked on, for example? With straight JavaScript, we’d probably do something like this:

jossWhedon.shows.map(function(show) {
  return show.title;
});
=> ["Dollhouse", "Dr. Horrible's Sing-Along Blog", "Buffy the Vampire Slayer", "Firefly"]

This isn’t so bad, but pluck allows us to simplify it even further:

_.pluck(jossWhedon.shows, "title");
=> ["Dollhouse", "Dr. Horrible's Sing-Along Blog", "Buffy the Vampire Slayer", "Firefly"]

FindWhere

findWhere returns the first value in a list that matches all of the listed key-value pairs. This can be useful for returning an object based on a dynamic value such as user input. So if we wanted to find a show based on its title, we would something like this without Underscore:

for (var i = 0; i < jossWhedon.shows.length; i++) {
  if (jossWhedon.shows[i].title === "Firefly") {
    var show = jossWhedon.shows[i];
  }
}
=> {title: "Firefly", characters: Array[2]}

Or we could shorten this into a single line of code using Underscore’s findWhere:

var show = _.findWhere(jossWhedon.shows, {title: “Firefly”});
=> {title: "Firefly", characters: Array[2]}

Reduce

Reduce condenses a list of values into a single value, just like JavaScript’s native reduce method (in fact, Underscore delegates to native methods when available). We can use it here to give us one array containing all of Whedon’s characters. It doesn’t look too different from non-Underscore reduce:

_.reduce(jossWhedon.shows, function(memo, show) {
  return memo.concat(show.characters);
}, []);
=>[{ name: "Echo", role: "doll" }, { name: "Topher", ... }, { name: "Billy", ...}, ...]

Why bother using Underscore’s methods where there are native equivalents? Aside from maintaining consistency across your code, you also gain the benefit of backwards compatibility with older browsers that may not support these native methods.

What you can do with it

Underscore's functions are useful enough on their own, but it's really when you chain them that you can start doing some powerful stuff.

Whedon’s Heroines

Let’s say you want to generate an array of all shows with female leads. Without Underscore, you’d end up with something that looked like this:

jossWhedon.shows.filter(function(show) {
  return show.femaleLead;
}).map(function(show) {
  return show.title;
});
=> ["Dollhouse", "Buffy the Vampire Slayer"]

This works, but it’s a little clunky. We can slim it down with Underscore:

_.pluck(_.filter(jossWhedon.shows, function(show) {
  return show.femaleLead;
}), "title");
=> ["Dollhouse", "Buffy the Vampire Slayer"]

Here we use filter to find the shows that have a female lead, and then pluck to extract their titles.

Counting Characters

What if you want to show some statistics on character distributions in the Whedonverse? How many love interests are there, for example, or how many slayers? With straight JS you might do something along these lines:

jossWhedon.shows.reduce(function(memo, show) {
  show.characters.forEach(function(character) {
    (!memo[character.role]) ? memo[character.role] = 1 : memo[character.role]++;
  });
  return memo;
}, {});
=> {doll: 1, mad scientist: 2, love interest: 2, slayer: 1, captain: 1, mechanic: 1}

Instead of using reduce to create our object, manually cataloging each role and then counting additional instances, we can use a single line of Underscore to achieve the same thing:

_.countBy(_.flatten(_.pluck(jossWhedon.shows, "characters")), "role");
=> {doll: 1, mad scientist: 2, love interest: 2, slayer: 1, captain: 1, mechanic: 1}

We use pluck to pull the character arrays from each show and flatten to condense them into a single array. Then we can use Underscore’s countBy method to catalog the instances of each role.

Mad Scientists? Muahaha!

Finally, let's catalog all the mad scientists that Whedon features in his shows. If we want a single object with each pertinent show as a key and its mad scientist as its value, our JavaScript might get a little unwieldy:

var isMadScientist = function(character) { return character.role === "mad scientist" };

jossWhedon.shows.reduce(function(memo, show) {
  if (show.characters.some(isMadScientist)) {
    show.characters.forEach(function(character) {
      if (isMadScientist(character)) {
        memo[show.title] = character.name;
      }
    });
  }

  return memo;
}, {});
=> Object {Dollhouse: "Topher", Dr. Horrible's Sing-Along Blog: "Billy"}

We can achieve the same results with very similar methods in Underscore, but the resulting code ends up a lot cleaner:

var isMadScientist = function(character) { return character.role === "mad scientist" };

_.reduce(jossWhedon.shows, function(memo, show) {
  if (_.some(show.characters, isMadScientist)) {
    memo[show.title] = _.detect(show.characters, isMadScientist).name;
  }

  return memo;
}, {});
=> Object {Dollhouse: "Topher", Dr. Horrible's Sing-Along Blog: "Billy"}

Here we’re using reduce again to condense our shows into a single mad-scientist-riddled object. For each show, we’re first checking if it has a mad scientist using some and our isMadScientist function. For each show that does indeed contain an evil genius, we find that character using detect, and then use their name as the value for the appropriate attribute (the show’s title) on our memo object.

More on Underscore

It’s easy to see even in these limited examples just how useful Underscore can be, which may explain why it’s used on sites like Reddit, Huffington Post, and LinkedIn, and has been included in Drupal 8 Core. Want to learn more? Underscore’s website is an excellent resource, and check out Michael Fogus’ Functional JavaScript, which uses Underscore to “to highlight and explain functional programming techniques.”

If you have any questions on the examples here, if you’ve used Underscore to simplify any of your projects, or even if you're just a big fan of Buffy the Vampire Slayer, we’d love to hear from you in the comments!