Thursday, July 21, 2011

JQuery Utility Functions - $.map()

A lot of devs use JQuery nowadays without realizing how many little utilities are sitting inside of it. You can save yourself some coding time, and a fair number of bytes, if you understand what's underneath the hood. You might even get better performance given the optimizations that have been made to these core functions. Top of mind are:

.data()
.grep()
.map()
http://api.jquery.com/category/utilities/

You might begin by asking, "Why don't I just get rid of these? I don't use them." Unfortunately they're core to how JQuery works, so if you use a little bit of it, these are coming along. May as well learn them.

Let's do .map() today.

.map() is basically a translation function for collections. You pass in an array and a function, and the function is used to translate the array into another array. If you've used .NET's LINQ before, .map() is the .Select(x => x...) of Javascript.

As a simple example, suppose you have an array of objects you received from the server:

var people = [
  { name: 'Joe', city: 'Amherst', ... },
  { name: 'Sarah', city: ... },
  ...
];

Now suppose you wanted to just get a list of names in the data. The obvious way is to loop through it, either with for(var i = 0...), or if you want to get fancy, $.each(), but either way it's about the same number of bytes for the user. Here's the $.each() code:

var names = [];
$.each(people, function() {
  names.push(this.name);
});

And of course you'll end up with something that looks like
var names = ['Joe', 'Sarah', ...];

Let's see it in .map()!

var names = $.map(people, function(person) {
  return person.name;
});

And you end up with the same result. When this gets minified, it's easily the byte-winner. It's also likely that browsers will continue to offer more fast aggregation built-in functions - as they do, .map() is likely to adopt them, and you get performance gains for free.

Here's a JSFiddle with the 3 approaches, if you want to experiment:
http://jsfiddle.net/XdWVx/

In a sense, $.map() is $.each() for when you know you're building a new array from the thing you're looping through. As you might guess, you can use this on collections of tags. So for example if you wanted to rapidly gather the elements of a form into an array of values:

var values = $('input[type=text]').map(function(input) {
  return input.value;
});

It's important to notice a troublesome jQuery inconsistency here. $.each() iterating over a list calls back to a function that takes 2 arguments, the index of the array and the value at that index. $.map() calls back to a function with just the one argument, and no index - those who value consistency may writhe at this.

This inconsistency worsens when you loop over an object. In this case, $.map's callback now takes 2 arguments, the property name and its value - but they're reversed from their order used in $.each():

http://jsfiddle.net/b9chris/MGvp3/

Since a lot of legacy code likely depends on this lack of consistency in jQuery, the only clean answer is likely a small library that sits on top of it that rights the ship and cleans up the mess. Something tells me I'm not the first OCD developer to notice this - perhaps it's already out there. Links to libraries on github or other places that solve this problem are welcome in the comments.

Happy mapping.

No comments:

Post a Comment