map() get() confusion

I'm just going through the jQuery API and I'm a bit confused on map() & get() method. I know I'm wrong but the map() method looks a lot like an .each() statement? Except the documentation says it returns a new jQuery object.

I've been playing with this on jsfiddle trying to get my head around it, but I'm not quite there. here is the jsfiddle link:

Also here is the snippet of code:

$.fn.equalizeHeights = function() {
    var two = $(this).map(function(i, e) {
                                return $(e).height();
                            });
    console.log(two);
    console.log(two.constructor);
    console.log(two.get());
    console.log(two.get().constructor);
    return this.height(Math.max.apply(this,two.get()));
}
$('input').click(function() {
    $('div').equalizeHeights();
});

I see they are extending jQuery using prototype to create a function called equalizeHeights(). And $(this) represents the selector for all the 'div' elements on the page. The map() call iterates through each of the items in the array of divs and returns its height? But what I'm confused about is what I'm logging to the console. One is an object and the other is an array?

Could someone elaborate on what map() and get() are doing in this snippet of code?

Thanks in advance.


Fundamentals

There are two different jQuery map() functions: .map(), and $.map(). They perform similar things, but over different collections. You're using the first form, which does the following:

  1. Iterate over the jQuery object (collection, whatever) on which the function was invoked. In this case, that's $(this), which is whatever the .equalizeHeights() function was invoked on ...which is $('div'): all <div> elements on the page (phew).
  2. Create an array with the same number of elements as the object that .map() was invoked on (all divs on the page, remember) whose nth element is generated by invoking the provided callback - I'll get there in a sec - on the nth element in the targeted jQuery object. In this particular case, that callback is this function:

    function(i, e) { return $(e).height(); }

Yes, .map() does look like .each(), but there is a key difference:

  • .each() performs an action on each of the elements in the collection; the return value of the callback passed to .each() is used to determine whether or not the iteration continues.
  • .map() also performs an action on each of the elements in the collection, but the callback's return value is used to generate an element in the array-like object returned by .map().

Are you still with me?

jQuery obects are array-like, but they are not arrays! The reason that you call .get() at the end of the .map() call is to turn that jQuery object into a true array. The elements of that array are the values returned by the callback.

Putting it all together

This function sets the height of every single <div> on the page to the height of the tallest <div>. Here's how:

$('input').click(function() {   // bind a click listener to every <input> element
    $('div').equalizeHeights(); // ...that will call the equalizeHeights() fn
                                //    on all <div> elements when fired
});

So, looking inside of the equalizeHeights() definition:

$.fn.equalizeHeights = function() {
    // construct an array that contains the height of every <div> element
    var two = $(this).map(function(i, e) {
                                return $(e).height();
                          });


    return this.height(    // set the height of element <div> element to...
        Math.max.apply(    // the largest value in...
            this,two.get() // the array of height values
        )
    ); // ...and finally, return the original jQuery object to enable chaining
}

But what about the constructor business?

As you discovered, yes, one is an object (a jQuery object) and the other is an array. That's why you need that .get() call to turn the array-like object into something that Math.max() can understand.

Instead of looking at the constructor property, you can use a little more jQuery to figure out just what you're looking at:

console.log(two.jquery);         // the version of jquery, something like "1.4.4"
console.log($.isArray(two));     // is it a plain old JS array? false
console.log(two.get().jquery);   // undefined! it's just an array.
console.log($.isArray(two.get()));    // true

Even better is to look at the actual objects inside of a debugger, rather than just console.log()-ing them. That way, you can see the entire object graph, all its properties, etc.

Any questions? Comment away.


map loops through a jQuery object and applies a function to each element. The return value of each call is added into an array. That array is then wrapped into a jQuery object and returned.

get returns an array containing each element in a jQuery object. This means that it essentially unwraps the selection returned by map and gets a plain JS array.

In your example, map creates a selection containing the height of each element. You then call get on it so that the native JS function Math.max can understand it. this.height() then sets the height of each element in the selection to the largest value in the array.