Things That Matter Most

Things That Matter Most

  • Home
  • Business
  • Faith
  • Technology
  • Politics
  • Creative Writing
  • Email
  • GitHub
  • RSS
  • Twitter

Powered by Genesis

Selector Caching in jQuery

August 1, 2014 by Eric

One of the first critiques I find myself writing while I review JavaScript code is “cache your selectors.”

Like me, many JavaScript developers are self taught and worked their way through a basic understanding of scripting through tutorials. Unfortunately, many tutorials sacrifice cleanliness for brevity, leading to functional snippets of code that look absolutely horrible in larger collections of code.

The worst offender is the way developers handle jQuery selectors.  Most commonly, I see things like $( this ) repeated several times within a callback function. Or $( '#selector' ) used throughout a script to both bind and unbind multiple events.

What new developers don’t realize is that every call to $( something } asks jQuery to rescan for the matching element, wrap it in a jQuery object, and create a new instance of something you already have in memory. If your code crashes due to memory cascades, overuse of redundant selectors might be related.

Actually, if you use a more advanced IDE like PhpStorm or WebStorm, the IDE itself will alert you to this issue so you can correct it.

Caching Selectors

In a small application, caching selectors isn’t that difficult. Just declare all of your objects at the top of your closure and use the new variables throughout your script.

var $window = $( window ),
    $document = $( document ),
    $footer = $( '#footer' ),
    $sidebar = $( '#sidebar' ),
    $images = $( 'img' );

Unfortunately, as the length of your application grows, the size of this selector cache grows and becomes difficult to manage. I’ve been experimenting with different forms of in-memory caching for JavaScript applications, and came up with a simple selector cache object that makes it easier to wrangle your references.

function Selector_Cache() {
    var collection = {};

    function get_from_cache( selector ) {
        if ( undefined === collection[ selector ] ) {
            collection[ selector ] = $( selector );
        }

        return collection[ selector ];
    }

    return { get: get_from_cache };
}

var selectors = new Selector_Cache();

// Usage $( '#element' ) becomes
selectors.get( '#element' );

Using selectors.get( something ) in your code from that point forward might be slightly more characters than the redundant selectors we’ve used previously, but it’s far more performant. Once you run code through a minifier, the character count difference will be negligible anyway.

Filed Under: Technology Tagged With: cache, javascript, jQuery

Like what you’re reading?

Consider pitching in to help cover the costs of maintaining this site and keeping my work 100% ad-free.

Close×

Comments

  1. daxserver says

    August 1, 2014 at 8:50 am

    Awesome! I never knew jQuery has to re-look the selectors. Thanks for that Eric 🙂

  2. Andy Adams says

    August 1, 2014 at 9:22 am

    Wow, I (almost) always cache my selectors using variables, but a Selector_Cache is a flippin’ awesome idea! Thank you, thank you for opening my mind.

  3. J.D. Grimes says

    August 1, 2014 at 9:29 am

    This is a great idea. So great, in fact, that it makes me wonder why something like this isn’t a part of jQuery already. I originally assumed that jQuery probably did use some kind of selector caching.

    • Andy Adams says

      August 1, 2014 at 9:34 am

      The only reason I can think of (and Eric can correct me if I’m wrong) is that jQuery can’t assume that a selector’s results won’t change. For example:

      $images = $( ‘img’ );

      If there was code for dynamically adding images to the page, the selection results would need to be refreshed after those images were added, so you can’t assume that the first ‘grab’ is always going to be complete.

      If you’re doing your JS in a $(document).ready callback, you typically will know that your selectors are going to stay the same, which is why you can cache them.

      • Eric says

        August 1, 2014 at 9:46 am

        That’s exactly why jQuery doesn’t cache things internally.

        • jtsternberg says

          August 15, 2014 at 6:40 am

          Great article. We can even allow the selector’s cache to be updated by adding a second parameter. I also changed ‘get’ to ‘$’ for solidarity. 😉

          https://gist.github.com/jtsternberg/1e03f5fd5be8427170c5

  4. Nombre23 says

    October 8, 2014 at 8:26 pm

    other posible solution:

    (function($){	
    	$.selector_cache = function (selector) {
    		if (!$.selector_cache[selector]) {
    			$.selector_cache[selector] = $(selector);
    		}
    		
    		return $.selector_cache[selector];
    	};
    })(jQuery);
    
    // Usage
    $.selector_cache('#elements');

    • Eric says

      October 8, 2014 at 8:49 pm

      That would work as well, and merely stores the cache as an attribute of the global jQuery object rather than as an object merely in the global namespace. Both are acceptable, and are doing the exact same thing.

  5. Stephen Johnston (@GLStephen) says

    October 20, 2014 at 4:54 pm

    Any reason a selector cache isn’t built into jQuery yet or at least something that can be turned on?

    • Eric says

      October 20, 2014 at 5:53 pm

      Because it’s not always a good idea. You can pass different parameters into jQuery – selectors, DOM objects, arrays, arbitrary HTML. jQuery is meant to do a very specific task, we just happen to use it in such a way that a selector cache makes sense in different situations.

  6. Madhavan Kumar says

    December 20, 2014 at 7:02 am

    when there is a change in the DOM tree, doesn’t the jquery selector tree still holding stale entries? does the cache needs to be flushed for each & every dom manipulation? it is a great tuts, i must say

    • Eric says

      December 20, 2014 at 4:13 pm

      If you’re making DOM updates that will affect a cached object, then you’ll need to re-cache that object as necessary.

  7. Alexey Shytikov says

    January 14, 2015 at 4:47 am

    Have you measured speed difference between approaches?

    • Eric says

      January 14, 2015 at 4:04 pm

      Not sure what different approaches you’re referencing. But I can tell you the difference in making a call to a cached selector versus an uncached selector is huge. jQuery does a lot of behind the scenes logic just to figure out what kind of selector you’re querying; by caching your selectors, you skip all of that logic.

      Caching selectors is actually more performant even than converting jQuery( '#selector' ) to jQuery( document.getElementById( 'selector' ) ). There’s more information on that selector performance and the rationale behind the change here: https://davidmichaelross.com/blog/call-jquerydocument-getelementbyidselector/

  8. Conor says

    February 11, 2015 at 6:47 am

    This is so great because it’s incredibly easy to plug into an existing project. Simple find-and-replace operations on selected areas from ‘$’ to ‘selectors.get’ had me running this solution in my project in under five minutes. The performance boost in my case was drastic. Thanks for the great article!

  9. Ricardo Ferreira says

    March 12, 2015 at 8:46 am

    Hi guys, excelente idea, but is ti possible to use Selector_Cache with dynamic content? or just with static content?
    Let’s suppose I have a dynamic combobox, does the Selector_Cache() keep track of the changes in the combobox or any other dynamic content?

    Regards,

    • Eric says

      March 13, 2015 at 9:04 am

      If you add new content, you’ll have to update the cache. It’s not auto-updating.

  10. Marouan Borja says

    September 30, 2015 at 7:27 am

    Nice function, and nice improvement !
    Thanks !

  11. Some Coward says

    November 27, 2015 at 2:17 am

    Horrible naming convention…
    But good idea 🙂

  12. jduhls says

    May 24, 2016 at 10:14 am

    Thank you for this! Gist’ed with credit: https://gist.github.com/jduhls/ceb7c5fdf2ae1fd2d613e1bab160e296

  13. Caelan Stewart says

    June 3, 2016 at 6:29 am

    Your comment is awaiting moderation.

    I wrote a script basically identical to this a while back, it’s all well and good, but the code is still just as messy as it was before. Selectors are not always so descriptive of what they are selecting, so using the selector as the key may cause maintainability difficulties later on, I’ve experienced it myself (except that $ was being called every time), and trying to identify what pieces of code like this scattered around are doing is awkward:

    $(‘#anc-82 a[data-prop=”val”]:parent’).toggleClass(‘active’);

    So, to avoid all of this, and increase the readability of your code by a long way, I still do what you originally did. Here is an example of how I typically stricture a new static JS class I write:

    var SomeModule = (function() {
        'use strict';
       
        var elements;
       
        function onHeaderLinkClick(event) {
            event.preventDefault();
           
            console.log('Header link was clicked!');
           
            return false;
        }
       
        function setListeners() {
            var iter = 0,
                headerLinksLen = elements.headerLinksLen;
           
            for(iter = 0; iter < headerLinksLen; ++iter) {
                elements.headerLinks[iter].removeEventListener('click', onHeaderLinkClick);
                elements.headerLinks[iter].addEventListener('click', onHeaderLinkClick);
            }
        }
       
        function cacheElements() {
            elements = { };
           
            elements.header = document.getElementById('wrapper');
            elements.headerLinks = elements.header.querySelectorAll('ul li a');
        }
       
        function someExposedMethod() {
            // Do something that changes DOM structure
           
            cacheElements();
            setListeners();
        }
       
        cacheElements();
        setListeners();
       
        return {
            someExposedMethod: someExposedMethod
        };
    })();
  14. Farzher says

    August 5, 2016 at 1:03 am

    I wrote some code similar to your example, but with a few more useful features

    Like finding within a context for example: `$$(‘p’).$$find(‘a’)`

    You can checkout the code on github
    https://github.com/farzher/jQuery-Selector-Cache

  15. StanGeorge says

    May 21, 2017 at 10:56 pm

    I’ve improved the function a bit in order to be able to clear the cache once an Ajax call is made on page.

    var Html_cache = (function () {
        var collection = {};

        /**
         * @constructor for Html_Cache
         * */
        function _Html_cache() { };

        _Html_cache.prototype.get = function (selector) {
            if (collection[selector] === undefined) {
                collection[selector] = $(selector);
            }
            return collection[selector];
        };

        /**
         * @method cleares cache
         */
        _Html_cache.prototype.clear = function () {
            collection = [];
        };

        return _Html_cache;
    }());

    var memo = new Html_cache();

    var muse = {};

    Here you will use something like :memo.get(selector).

    In case of ajax call or dom manipulation all you need to do is use:
    memo.clear().

    I hope it helps.

  16. Michael Crenshaw says

    August 29, 2017 at 12:07 pm

    Is there a potential memory cost to storing in the collection variable vs. just storing the jQuery object in its own variable? I would think that the latter would be garbage-collected when no references to the variable remain, whereas the former would stick around. Am I too pessimistic about the intelligence of JS engines?

    • Eric says

      August 29, 2017 at 1:12 pm

      It depends on your use case. If you’ve only got a handful of selectors, by all means, store them individually and they’ll be garbage collected when necessary. Note that this article is a little over 3 years old – i.e. before React, Vue, and the like had taken over how we build front-ends. In many use cases at the time, the number of selectors you had to cache grew to be unwieldy, so a collection variable in memory was more useful.

      Not more efficient in terms of memory management, but more efficient in terms of development time and knowing what needed to go where.

  17. Anh Tran says

    January 21, 2018 at 11:46 pm

    Just found your post. Great concept!

About Me

Eric is a storyteller, web developer, and outdoorsman living in the Pacific Northwest. If he's not working with new tech in web development, you can probably find him out running, climbing, or hiking in the woods.

Get the newsletter