Peeling Away the jQuery Wrapper and Finding an Array
read 19 commentsIf you haven't poked around under the hood of jQuery, you might not be aware that when you pass the jQuery function an expression or DOM element it places these elements (or, possibly a single element) into an object, and then this object is returned so that it can be chained. Without getting into the details of chaining, the fundamental concept to remember is this:
Most jQuery methods return the jQuery object itself, which allows methods to be chained.
The interesting thing about the jQuery object is that while its datatype is an object, it has array-like characteristics:
- its property names (the ones that refer to DOM elements, at least) are numeric
- it has a length property
Have you ever noticed, for example, a snippet of jQuery code that directly accesses a DOM element by using the bracket operator with the jQuery function?
- $('div')[0]; // give me the first DOM element that the
- // jQuery function found
Now, the jQuery function returns an object, but it looks like we just treated it as an array. It can also be written like this:
If this is confusing, let's see if we can clear it up with some code. Go to the jQuery.com homepage, open up Firebug using Firefox, and run the following line of code in the console.
- $('div').toSource(); // toSource() is a method of all JavaScript objects
You should be looking at this in the console:
"({length:25, 0:{}, 1:{}, 2:{}, 3:{}, 4:{}, 5:{}, 6:{}, 7:{}, 8:{}, 9:{}, 10:{}, 11:{}, 12:{}, 13:{}, 14:{}, 15:{}, 16:{}, 17:{}, 18:{}, 19:{}, 20:{}, 21:{}, 22:{}, 23:{}, 24:{}, prevObject:{0:{}, length:1}})"
Looky there! The jQuery function is returning an object with a bunch of properties. If we tack on the bracket operator to the jQuery function, which returns an object, we can access the properties of that object because inside the object are properties with a numeric property name. Fascinating isn't it?
I know what you're thinking: this tutorial is about arrays, not objects. Right, so let's get to the array part. Since we now know that the jQuery function returns an object that only masquerades as a JavaScript array the question at hand is how do I get a true JavaScript array from a jQuery wrapper object. This is actually very simple. Most of you might not have been aware (if not, I showed you earlier) that $('div')[0] and $('div').get(0) return a reference to the first element in the jQuery wrapper object. But, the get() method actually has another purpose. It can quickly turn the DOM elements found in the jQuery wrapper into an array of DOM elements.
Let's return to jQuery.com and open up Firebug again. Inside the console run the following line of code.
You should now be looking at this in the firebug console:
"[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}]"
Essentially the .get() method placed all of the DOM elements inside the jQuery wrapper into an array and returned that array. So now you can use default array methods on your array of elements. For example:
- //this is a method of the array object
Additionally, since we now have an array we can write a statement like this:
There is something worth noting here that might not be obvious to those starting out with jQuery or JavaScript. Since we are now returning an array, the chain is broken. You can't chain any jQuery methods after using the .get() method.
But, heck, you have an array. Now you can use the utility functions that jQuery provides ($.each(), $.grep(), $.map(), $.inArray(), $.unique()), as well as core JavaScript methods and properties, to work on the array. I'm not going to demonstrate all of the jQuery utility functions that can be used on an array, but I do want to examine one of them here in this tutorial.
The $.each() utility function, not to be confused with the $('selector').each() method used to operate on a set of DOM elements in a jQuery wrapper, can be used to seamlessly iterate over an array or object. For example, let's say that you would like to store (or cache) a set of <li> elements in an array, because maybe you are going to remove them from the DOM temporarily but eventually will be placing them back on the page. To do this you could use the $.each() utility function to loop over an array and append each <li> element back to the page. In the code example below this is done on page load by converting and storing the jQuery wrapper of <li> elements to an array, and then looping over this array in order to place them back on the page.
- <html>
- <head>
- <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js"></script>
- </head>
- <body>
- <ul>
- <li>task 1</li>
- <li>task 2</li>
- <li>task 3</li>
- <li>task 4</li>
- <li>task 5</li>
- <li>task 6</li>
- <li>task 7</li>
- <li>task 8</li>
- </ul>
- <script>
- var arrayList = $('ul li').get();
- $('ul').empty();
- $.each(arrayList, function(){
- $('ul').append('<li><del>'+$(this).html() +'</del> complete</li>');
- });
- </script>
- </body>
- </html>
Notice that I am forgoing the use of the .ready() event by simply placing my jQuery code before the closing body element.
So, hopefully it's obvious how extremely handy it can be at times to make an array out of a jQuery wrapper. In fact, once you are dealing with an array, you can use a handy plugin called Rich Array. This plugin provides the following additional utility functions for arrays. And yes, there is a bit of duplication here with some of the core utility functions.
$.richArray.in() //Checks whether an array contains some value
$.richArray.unique() //Produces the duplicate-free version of the array
$.richArray.diff() //Finds the difference between two arrays.
$.richArray.intersect() //Finds the intersection of two arrays
$.richArray.filter() //Applies filter to the array, using callback function
$.richArray.map() //Applies callback function for each element in the input array, and returns array of values that this function returned
$.richArray.sum() //Computes the sum of all array elements
$.richArray.product() //Calculates the production of all elements of the array
$.richArray.reduce() //Reduces the array. One-element arrays are turned into their unique element
$.richArray.compact() //Creates new version of array without null/undefined values
$.richArray.without() //Creates a new version of the array that doesn't contain the specified value
















Maybe it's worth mentioning too that $.each can loop over any object with a for..in loop but it doesn't do that on a jQuery object only because it has a length property that indicates that it's an array-like object so the non-integer properties shouldn't be enumerated so a simple for loop is used instead of for..in, treating it like a simple array.
It should be pointed out that .toSource() is not supported in Safari, Chrome, or IE. :)
Hey Cody,
I believe the following also works:
I'm not sure if there is a disadvantage over
.get(0), but I figured I would throw it out there as a short alternative when you already have a reference to your element.Another thing to mention is that $.each isn't necessary when creating events on DOM objects, as jQuery automatically iterates over every DOM element in the jQuery collection and sets the event on all of them. Example:
instead of what I have seen some people new to jQuery do:
The same goes for almost every jQuery method: .css, .attr, .etc...
@Josh, $.each() and $().each() are different functions (although the functionalities are similar).
This is a great insides Cody, thanks. This basically combines easiness of jQuery selectors and performance of native browser traversing. This is will probably boost performance of some of my previous projects where I had a very long lists and tables.
Thanks again,
jQuery Lover :)
Yeah, sorry, typo. Thanks for the catch. For those playing at home...
$().each(function) iterates over the selected DOM elements and executes the function on each of them.
$.each(array,function) iterates over an array and executes the function.
In each (pun intended) case "this" will get you the current element of the array and in the $() case, it is the unextended DOM element so if you want access to jQuery functions on it, you will need to wrap it in a $(this).
tl;dr
you are aware that firebug won't display array with non-numerical (associative) arrays correctly? This has caused some headaches for some people in the past and there are enough debates on wether you should use associative arrays or objects in the first place...
in mootools for example you could simply..... nevermind, i'm drifting off ;)
toSource() is a Firefox extension -- it isn't part of any spec -- toString() is the only (similar) function that's in ECMA262
I wrote the same thing back in April:
http://codeclimber.net.nz/archive/2008/04/11/Beware-the-.-in-jQuery-elementId--document.getElementByIdelementId.aspx
Any idea on how to incorporate a "toggle all" feature into this? I've been trying to figure it out, but not very experienced with Javascript or Jquery.
Wouldn't you know it - I worked on this for quite a while, and then an hour after posting my question above, i figured it out. My problem was that if you checked on one of the regular checkboxes, and then checked the "check all" one, it would toggle the class to off. Anyway, here it is (probably could be cleaned up, but like i said above, I'm not very experienced with this stuff. I just added this to the head section
and gave the row with the checkall box id = trcheckall and the input id = all.
Why is this in the beginner's category? This is far too complicated for a beginner like me!
Thanks for taking the time to write it, but I'm so foncused :$
heh. Yeah, I suppose you're right. I just changed it to Intermediate/Advanced. Thanks for the feedback.
your code highlighter sucks balls. it's not trusting the html and converting line breaks to p's and different characters to their html codes. lame. i can't say much, because i don't have my own code highlighter and have to resort to using dpaste.com.
Well, you don't have to be a jerk about it.
Okay, I think it's fixed now.
@Cody: Thanks for this insight. You just spared me a considerable amount of experimentation - and time.
@Karl: LMAO at that last response, man.
Hi Cody,
Great tutorial. Thanks.
We've added you to the codebix programmer's blog aggreagator. Do have a look and send us your feedback.
Regards
Thanks for the info on .get(), Cody. Previously, I've just been adding a [0] to my jQuery objects, but certainly prefer a pure approach rather than a jQuery/JS frankenstein, where possible. Thanks again.
thanks for making if clear this options require advanced jquery experience not to mention i'm still a newbie when it comes to scripting .I will manage this to put it to my site.
http://bit.ly/scriptmethis
thanks,
chloe andrea
follow me at facebook
facebook.com/chloe