Last Updated: February 25, 2016
· benhowdle89

Memoization with DOM selectors in JavaScript

function getEls(sel) {
    if (!getEls.cache) getEls.cache = {};
    if (getEls.cache[sel]) {
        console.log('from cache');
        return getEls.cache[sel];
    var r = document.querySelectorAll(sel || '☺'),
        length = r.length;
    return getEls.cache[sel] = (length == 1) ? r[0] : r;

var imgs = getEls('img');
var moreImgs = getEls('img');
var ps = getEls('p');
var morePs = getEls('p');

3 Responses
Add your response

I was thinking about this too but then there is a problem if user uses:

  1. "#header .menu-item"
  2. "#header"
  3. ".menu-item"
  4. ""

or just typo:
1. "#header .menu-item"
2. "#header .menu-item"

But for smart people this is really useful ;)

over 1 year ago ·

Nice. There's a couple of things I'd tweak though.

  • The function is called getEls, but you sometimes return multiple elements, and sometimes only one. It should be consistent, so that the user of the method doesn't have to check if what they got back was a NodeList or some kind of Element.
  • I have no proof of this, but I suspect passing sel || ☺ is inefficient. It'd be better to return early if no selector is passed. You could even return an empty NodeList using document.createDocumentFragment().childNodes
  • I'd recommend taking a boolean argument that, if true, forces it to bypass the cache and save that selector again.
over 1 year ago ·

What if elements are added dynamically to the page, after the initial cache has been created?

For example,

var imgs = getEls('img');
// some AJAX stuff injects more images to DOM
imgs = getEls('img') // won't this return outdated result from cache?
over 1 year ago ·