Using jQuery’s .pushStack() for reusable DOM traversing methods
read 3 commentsThe .pushStack() method has been in jQuery since before version 1.0, but it hasn't received a whole lot of attention outside of core developers and plugin authors. While its usefulness may not be immediately apparent, it can come in really handy in some situations, so I'd like to take a quick look at what it does, how it works, and how we can use it.
pushStack Basics
At its most basic level, the .pushStack() method accepts an array of DOM elements and "pushes" it onto a "stack" so that later calls to methods like .end() and .andSelf() behave correctly. (Side note: As of jQuery 1.4.2, you can pass in a jQuery object instead of an array, but that isn't documented and jQuery itself always uses an array, so that's what we'll stick to here.)
Internally, jQuery uses .pushStack() to keep track of the previous jQuery collections as you chain traversing methods such as .parents() and .filter(). This lets us traverse through the DOM, do some stuff, "back up" to previous collections within the same chain using .end(), and then do something else. Here is a somewhat contrived example:
- // select some divs
- $('div.container')
- // find some spans inside those divs and add a class to them
- // pop those spans off the "stack",
- // returning to the previous collection (div.container)
- // add a class to the parent of each div.container
Because .find() returns the result of a .pushStack() call to keep track of the previous collection (as does .parent()), we can use .end() in the above example to return to the container divs.
Using pushStack for Fun and Profit
So, this is great for jQuery, but what can .pushStack() do for me and my code? Well, it can help me write specialized DOM traversal plugins that act just like jQuery's own traversal methods. In other words, I can stop chaining the same sets of traversal methods together and instead write a reusable function that still works with with .end() and all that. For example, let's say I often have a need to find an element's grandparent. While I could write $('#myElement').parent().parent() every time, it might be nice to just be able to write $('#myElement').grandparent() instead. A naïve way to write a grandparent plugin would look like this (changing the method name to "grandpa" for this example):
The problem here is that two new jQuery object instances are added to the stack. So, let's see what happens when we use it:
Without seeing the plugin, we would expect to see "child son" inserted into <div class="son">, but "pa" is inserted instead. Each .parent() call in the plugin adds to the stack, so using .end() only pops the second one off.
If we use .pushStack() instead, however, we can achieve the expected behavior:
Within a plugin function, one that is a method of $.fn, the this keyword refers to the jQuery object; therefore, the els variable refers to a jQuery object, as well. To convert it to an array, we use jQuery's .get() method, and we pass that array to .pushStack(). Let's see if .grandma() works any better than .grandpa().
Here, "child daughter" is inserted, which means that .end() works as expected, changing the jQuery collection from the result of .grandma() to the result of $('div.daughter'). So, we've just successfully written a DOM traversal plugin, albeit a very simple one.
The Simplest DOM Traversal Methods
If the plugin only uses one DOM traversal method, then .pushStack() isn't really necessary. The HTML5 data filter plugin written by Elijah Manor illustrates this point nicely:
Only one new jQuery collection is added to the stack, via .filter(), so using .end() simply pops that one off, and our job is done.
Filtering grandparents
For the sake of completeness, it would be nice for this DOM traversal plugin to allow optional "filtering" of the parent and grandparent elements. After all, jQuery's .parent() and .parents() allow filtering. For example, if I were to write $('div.child').parent('.daddy'), the jQuery collection would only contain an element if div.child had a parent element and if that parent had a class of "daddy."
There are plenty of reasonable ways one could include the filters, but for my purposes I'm going to have a .grandparent() method optionally accept two arguments. If only one argument is provided, it will filter the grandparent element only; if two are provided, the first will filter the parent and the second will filter the grandparent. Here is the full plugin plugin:
Finally, we have a nice .grandparent() plugin that adheres to the contract set by other jQuery DOM traversal methods—one that works with both filters and the .end() method. Here is what it could look like in use.
















Any comments on .end() vs caching selectors? (eg: parent = $("#parent") )
Best practice? Performance improvement?
This is an awesome post - thanks for sharing the info on pushStack() as this can be used to help performance when writing code that does heavy dom manipulation.
@Esteban - without digging too deep, this would be just as performant as "caching" selectors because the essentially that is what pushStack is doing. end() just pops the stack.
Regarding best practices, I would say, don't get too carried away and have someone have to read through a nested chain with 15 end statements in it and I would say, it's a good practice to use pushStack when writing functions as the jquery team has put this method in place most likely for performance reasons and to ensure that you can easily get back to your parent elements. This allows you to traverse and manipulate the dom based on convention instead of configuration also.
Just my 2 cents...
@Dan thanks for your reply!
After seeing this post I created a jsperf test to see if there was any difference in performance:
http://jsperf.com/jquery-end-vs-cached-selector
only ran it on Chrome and the seem to perform the same.
But I agree on the point of not chaining 15 end() calls. I haven't changed any code yet to use end(), but it's really good to know it exists