62

For example, will the first piece of code perform a full search twice, or is it smart enough to cache results if no DOM changes have occurred?

if ($("#navbar .heading").text() > "") {
  $("#navbar .heading").hide();
}

and

var $heading = $("#navbar .heading");

if ($heading.text() > "") {
  $heading.hide();
}

If the selector is more complex I can imagine it's a non-trivial hit.

BoltClock
  • 700,868
  • 160
  • 1,392
  • 1,356
Allain Lalonde
  • 91,574
  • 70
  • 187
  • 238
  • In my opinion, it should cache unique selectors, such as ID's. It would also be nice if there were a plug-in that allows caching of all other selectors. – Donald T Feb 27 '13 at 16:20
  • 1
    jQuery team indicated it here. https://learn.jquery.com/using-jquery-core/selecting-elements/#saving-selections – trungk18 Aug 10 '17 at 08:52

14 Answers14

30

Always cache your selections!

It is wasteful to constantly call $( selector ) over and over again with the same selector.

Or almost always... You should generally keep a cached copy of the jQuery object in a local variable, unless you expect it to have changed or you only need it once.

var element = $("#someid");

element.click( function() {

  // no need to re-select #someid since we cached it
  element.hide(); 
});
Dan Heberden
  • 10,990
  • 3
  • 33
  • 29
gnarf
  • 105,192
  • 25
  • 127
  • 161
  • quick question on the element.hide();. Since that it is in a closure is it better to do element.hide() or $(this).hide()? I am not sure how much extra time is spent in wrapping it in a jquery object as opposed to crawling the scope chain. – uriDium Apr 10 '13 at 08:53
  • 2
    `element.hide()` is generally better unless you want the closure to work on multiple elements, and therefore tie it up to `$(this)`. Though if you are concerned about "performance" of either of these things, you're suffering from a case of unrealistic optimizations. Neither scope chains, or a `$(this)` is going to be a bottleneck in any application, ever. – gnarf Apr 10 '13 at 23:54
  • 1
    ALWAYS??? http://jsfiddle.net/0zrtcfp9/ I would said only when needed and to be aware of variable reference, scoping and memory usage – A. Wolff Dec 09 '15 at 09:21
16

jQuery doesn't, but there's the possibility of assigning to variables within your expression and then use re-using those in subsequent expressions. So, cache-ifying your example ...

if ((cached = $("#navbar .heading")).text() > "") {
  cached.hide();
}

Downside is it makes the code a bit fuglier and difficult to grok.

Peter Olson
  • 139,199
  • 49
  • 202
  • 242
Neil C. Obremski
  • 18,696
  • 24
  • 83
  • 112
  • 5
    It doesn't have to, in many cases it makes it cleaner to use variables to store your selected elements in cleanly named groups. – jondavidjohn Jul 08 '11 at 16:07
  • 3
    I just wrote a lightweight plugin that can cache selectors for you, while leaving your code clean. https://github.com/farzher/jQuery-Selector-Cache – Farzher Sep 01 '12 at 03:18
  • 3
    Note: Please be sure you guys use the `var` keyword when declaring variables so they don't get hoisted into the global scope unnecessarily. – sic1 Sep 22 '15 at 22:59
15

It's not so much a matter of 'does it?', but 'can it?', and no, it can't - you may have added additional matching elements to the DOM since the query was last run. This would make the cached result stale, and jQuery would have no (sensible) way to tell other than running the query again.

For example:

$('#someid .someclass').show();
$('#someid').append('<div class="someclass">New!</div>');
$('#someid .someclass').hide();

In this example, the newly added element would not be hidden if there was any caching of the query - it would hide only the elements that were revealed earlier.

JoeBloggs
  • 1,467
  • 9
  • 8
  • 5
    It could detect changes to the dom between calls and invalidate the cache. If that's how it was designed though. – Allain Lalonde Nov 16 '08 at 20:57
  • Sounds like more overhead to me - I'd imagine that searching was much more open to shortcuts than monitoring the entire DOM. But who knows? :) – jTresidder Nov 26 '08 at 02:48
  • Yeah, agreed, didn't mean a full DOM scan, just meant that if you restricted yourself to changed the DOM using jQuery, you could detect changes. – Allain Lalonde May 17 '09 at 17:16
12

I just did a method for solving this problem:

var cache = {};

function $$(s)
{
    if (cache.hasOwnProperty(s))
    {
        return $(cache[s]);
    }

    var e = $(s);

    if(e.length > 0)
    {
        return $(cache[s] = e);
    }

}

And it works like this:

$$('div').each(function(){ ... });

The results are accurate as far as i can tell, basing on this simple check:

console.log($$('#forms .col.r')[0] === $('#forms .col.r')[0]);

NB, it WILL break your MooTools implementation or any other library that uses $$ notation.

Aleksandr Makov
  • 2,820
  • 3
  • 37
  • 62
  • Why if(e.length > 0). This will return undefined if no elements are selected. – Fuji Jul 25 '13 at 11:53
  • This is done to avoid populating the cache store with empty data. You can easily change the logic. The example above isn't meant to be used as is, but rather adopted to your app needs. You may also notice that it does not support all of the arguments that original `jQuery();` does. – Aleksandr Makov Jul 25 '13 at 18:47
9

I don't think it does (although I don't feel like reading through three and a half thousand lines of JavaScript at the moment to find out for sure).

However, what you're doing does not need multiple selectors - this should work:

$("#navbar .heading:not(:empty)").hide();
Peter Boughton
  • 110,170
  • 32
  • 120
  • 176
6

Similar to your $$ approach, I created a function (of the same name) that uses a memorization pattern to keep global cleaner and also accounts for a second context parameter... like $$(".class", "#context"). This is needed if you use the chained function find() that happens after $$ is returned; thus it will not be cached alone unless you cache the context object first. I also added boolean parameter to the end (2nd or 3rd parameter depending on if you use context) to force it to go back to the DOM.

Code:

function $$(a, b, c){
    var key;
    if(c){
        key = a + "," + b;
        if(!this.hasOwnProperty(key) || c){
            this[key] = $(a, b);
        }        
    }
    else if(b){
        if(typeof b == "boolean"){  
            key = a;  
            if(!this.hasOwnProperty(key) || b){
                this[key] = $(a);
            }
        }
        else{
            key = a + "," + b;
            this[key] = $(a, b);   
        }            
    }
    else{
        key = a;
        if(!this.hasOwnProperty(key)){
            this[key] = $(a);
        } 
    }
    return this[key]; 
}

Usage:

<div class="test">a</div>
<div id="container">
    <div class="test">b</div>
</div>​

<script>
  $$(".test").append("1"); //default behavior
  $$(".test", "#container").append("2"); //contextual 
  $$(".test", "#container").append("3"); //uses cache
  $$(".test", "#container", true).append("4"); //forces back to the dome
​
</script>
Sefi Grossman
  • 71
  • 1
  • 5
  • generally bad to use variables `a` and `b` or `x` and `y` in almost every language as they are commonly used internal variable names, but this is a good start to a plugin – vol7ron Mar 19 '13 at 19:20
  • 1
    To get here: `if(!this.hasOwnProperty(key) || b){` b must always be `true` because you do `else if(b)` (truthy) and then `if(typeof b == "boolean")`, which guarantees it is a boolean. The only truthy boolean is `true` – vol7ron Mar 19 '13 at 19:33
  • also `this` will be `window` in IE and earlier versions do not have `hasOwnProperty` thus you must `Object.prototype.hasOwnProperty.call(this,key)` – vol7ron Aug 01 '13 at 23:02
4

This $$() works fine - should return a valid jQuery Object in any case an never undefined.

Be careful with it! It should/cannot with selectors that dynamically may change, eg. by appending nodes matching the selector or using pseudoclasses.

function $$(selector) {
  return cache.hasOwnProperty(selector) 
    ? cache[selector] 
    : cache[selector] = $(selector); 
};

And $$ could be any funciton name, of course.

Joschi
  • 2,874
  • 1
  • 18
  • 23
4

i don't believe jquery does any caching of selectors, instead relying on xpath/javascript underneath to handle that. that being said, there are a number of optimizations you can utilize in your selectors. here are a few articles that cover some basics:

rymo
  • 3,285
  • 2
  • 36
  • 40
Owen
  • 82,995
  • 21
  • 120
  • 115
2

There is a nice plugin out there called jQache that does exactly that. After installing the plugin, I usually do this:

var $$ = $.q;

And then just

$$("#navbar .heading").hide();

The best part of all this is that you can also flush your cache when needed if you're doing dynamic stuff, for example:

$$("#navbar .heading", true).hide(); // flushes the cache and hides the new ( freshly found ) #navbar .heading

And

$$.clear(); // Clears the cache completely

pyronaur
  • 3,515
  • 6
  • 35
  • 52
  • Nice! Does the $$("#navbar .heading", true) repopulate the cache too? – Allain Lalonde Oct 31 '14 at 14:25
  • Yep! It immediately returns the new result and caches it. See the site of the plugin - it has even more advanced stuff in it that I haven't even used, like selector lists ( so that you can group them and clear only a specific kind ) etc. - but getting into it is really simple and I've found that I don't really need the more advanced stuff :) – pyronaur Nov 03 '14 at 09:56
2

jsPerf is down today, but this article suggests that the performance gains from caching jQuery selectors would be minimal.

enter image description here

This may simply be down to browser caching. The selector tested was only a single id. More tests should be done for more complicated selectors and different page structures...

joeytwiddle
  • 29,306
  • 13
  • 121
  • 110
2

John Resig in his Jquery Internals talk at jQuery Camp 2008 does mention about some browsers supporting events which are fired when the DOM is modified. For such cases, the Selctor results could be cached.

1

jQuery Sizzle does automatically cache the recent functions that have been created from the selectors in order to find DOM elements. However the elements themselves are not cached.

Additionally, Sizzle maintains a cache of the most recently compiled functions. The cache has a maximum size (which can be adjusted but has a default) so you don’t get out-of-memory errors when using a lot of different selectors.

Brave Dave
  • 1,300
  • 14
  • 9
1

$.selectorCache() is useful:

https://gist.github.com/jduhls/ceb7c5fdf2ae1fd2d613e1bab160e296

Gist embed:

<script src="https://gist.github.com/jduhls/ceb7c5fdf2ae1fd2d613e1bab160e296.js"></script>
jduhls
  • 693
  • 7
  • 11
0

Check if this helps https://plugins.jquery.com/cache/

Came across this as part of our regular project

Sarath.B
  • 415
  • 4
  • 6