3 jQuery Nuggets
In this post, I will present 3 tiny jQuery plugins. They are present in almost all of my jQuery adorned pages. At least theoretically this means they might be worth Your time.
I call these plugins “nuggets”, because they are so small but very precious. They can save You a lot of unhappy jQuery debugging time.
Flush
With jQuery it is very easy to append/prepend/clone any amount of complex html, and in the process create in memory a lot of dom nodes, and thus to confuse the browser garbage collector immensely.
1 2 3 4 5 6 7 8 9 10 |
/* do this and watch memory consumption rising and staying there, even long after the loop is finished. or long after you have left the offending page */ while ( some_large_number -- > 0 ) { var $doc = $().append("some complex html") .remove("whatever") .children().clone() ; } |
Above code is really bad jQuery code, and I do sincerely hope very rarely seen(?). But. You never know when you will notice your shiny and complex jQuery based page, is starting to cause mysterious 100+ MB RAM consumption. For this kind of situations, this “nugget” flush()
plugin is a real jQuery DOM leaks “basher”. This “nugget” has made me (almost) completely forget about the bad old days when we wondered to no end, why is one page becoming so unresponsive in IE, or why is memory consumption of a browser rising so dramatically, when hosting some complex jQuery based page. So, here it is:
1 2 3 4 5 6 7 8 9 10 |
/* $().flush() re-makes the current element stack residing inside current jQuery instance thus flushing-out non-referenced elements, removed elements, etc. all left inside after numerous remove's, append's etc ... */ jQuery.fn.flush = function() { return jQuery(this.context).find(this.selector); } |
While developing any more complex page, it is very easy to slide into the “DOM leaks” black hole. This is mainly because inevitably in such pages, we do a lot of, so called “DOM manipulations”. Usually xml/json message comes from a server side, we make the html markup from it, we then perhaps use jQuery to remove some div contents, create new dom elements attach them inside the div “container”, then proudly style and display the result. And so on, as user works on this same page, until user does not leave to the next page.
Especially the DOM element removal is highly debatable. From the W3C spec, where it was somewhat naively designed, all the way to modern jQuery based implementations. Ditto … DOM element remove()
and browsers are not very good friends.Yes, modern HTML5 browsers too. Especially when JavaScript is mixed-in. The key difficulty, browsers are facing, is to know when is an DOM element not in use any more? Here I do not mean “detached”, I mean “not in use”. That is: When any given DOM node can be actually removed from memory. And of course poor old IE, is (was?) the worst one in this game. The Usage:
1 2 3 4 5 6 7 8 9 10 |
/* add the flush() call, and watch memory consumption rising a little and going down quickly, after the loop is finished. */ var $doc ; while( large_num-- > 0 ) { $doc = $().append("some complex html") .remove("whatever").flush() .children().clone().flush() ; } |
Unbindall
But, still this is not a complete jQuery and dom leaks solution. There are DOM events too. jQuery handles events very effectively. One can get carried away easily and forget to “dispose of event objects safely” when finished. This is and is always going to be a source of lot of grief, and not just for begginners. Forgeting to relase events is also a cause of thousands “calls for help” on numerous jQuery forums. And even angry jQuery “bashings” in forums, if calls are not promptly answered. For my pages, I have solved the problem of not released events with this following tiny plugin.
1 2 3 4 5 |
/* remove all event bindings , and the jQ data made for jQ event handling */ jQuery.unbindall = function () { jQuery('*').unbind(); } |
The key issue with event releasing is “where to do it”. Not how to do it. Without detouring into that subject, I will just offer this solution:
1 2 3 |
$(window).unload(function() { jQuery.unbindall() }); |
The above can be a “real life saver” for IE hosted jQuery enabled pages with any ammount of feverish jQuery DOM event binding.
T Junction
And now, and at last a tiny plugin that is not invented for combating DOM leaks. This one is for allowing long and uninterrupted and happy jQuery chaining. A favorite sport of many “jQueryans”.
1 2 3 4 5 |
jQuery.fn.$T = function(cb, what) { if ("function" !== typeof cb ) return this ; cb.apply( this, what || [] ); return this; } |
What is going on here? The above plugin takes callback and whatever you need and gives it to the callback as its argument. And then simply returns the jQuery current instance, allowing for the chain to proceed.
If called without arguments T$ will “do nothing”. Ok, but why is this usefull? Here is why: $T() plugin gives you an simple chain interrupt-and-proceed opportunity. In your beloved and long jQuery chains. Consider this, very frequent jQuery pattern:
1 2 |
$("whatever").find("something") .each(function() { /* */ } ) ; |
What happens above if selector “whatever” produces empty jQuery instance? Above, can be a very complex find(). We may want, for example, to log the result of a find, or do something if one thing is found but not the other, or anything else, before chain goes into potentially lengthy jQuery stack of elements, to be used by each(). Without $T()
aka “T junction” , we would need to do something like this.
1 2 3 4 5 6 7 8 9 |
$found = $("whatever").find("something"); /* break the chain to check if some elements are found or not found ? */ if ( $found.length < 1 ) { console.warn( "Found no elements using 'whatever'?"); } else { // operate on each element found $found.each(function() { /* */ } ) ; } |
With the help of $T()
, we have much cleaner and uninterrupted chain.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
/* callback for $T() */ function log_or_loop ( callback ) { /* "this" is passed by $T() callback is given as second argument to $T() */ var $this = $(this) ; if ( $this.length < 1 ) { /* log the warning*/ } else { $this.each( callback) ; } }; // use the T$() junction $found = $("whatever").find("something") .$T( log_or_loop , function() { /* arg for log_or_loop */ } ) .append("whatever else") ; |
Above is (by the way) nice, useful and eminently reusable mechanism of using jQuery.each(), built on top of this tiny $T() plugin.
Obviously inside the callback argument given tor $T()
, we will want finish as quickly as possible, in real life situations, so that the whole chain is not slowed down, inside the T$()
call.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// called from $T(), returns "immediatelly" function log_findings () { var $host = this ; // jQuery host var tid = setTimeout( function () { clearTimeout(tid) ; log_elements_found( $host ) ; },1) ; }; // do not interrupt the chain to // log something $found = $("whatever").find("something") .$T( log_findings ) .append("whatever else") ; |
This is it. 3 tiny and precious jQuery plugins for Your jQuery code. Not a lot of code, just a lot of fun :)
NOTE: This post was originally posted 2009 Oct 02.
5 thoughts on “3 jQuery Nuggets”
Thanks! Saved me a lot of worry about the memload of my application!
Hi.
You should’t use flush function – look at ticket #6754 at jQuert bug Tracker.
Selector property only concatenates used selectors, for:
test = jQuery(‘div’).find(‘span, a’)
selector property for test jQuery object would be ‘div span, a’ when you would expect
something like ‘div span, div a’.
So test is not usualy equal to $(test.context).find(test.selector).
(its old post but maybe this comment will help somebody who would like to use this code)
@Luke
Fascinating. Is this still the case inside jQuery the latest? I am bit not sure when I see the words “usualy not equal” …
My
flush()
depends on the fact thatfind()
actually is single point from where jQuery uses Sizzle (it’s selector engine) after all various forms and entry points through which one can use jQuery to actually query the DOM tree. Also find also replaces the current jQ stack.[sourcecode lang=”js”]
/*
A is element id, of the element that is
nested in the document body
*/
"A" === $(document.body).find("#A")[0].id
// true
[/sourcecode]
I hope this helps ?
Similar to your T junction is the “tap” method from Ruby and underscore.js
@timb
I did not know for underscore.js “tap” method? Interesting.
What I found is that presenting several simple but well developed use cases explains these kind of combinatorial patterns very well.
Do you have one such use case in mind ?