22

I repetitively use document.getElementById a lot on common CSS elements.

Would there be a significant performance gain if I created a global array to store all of my document.getElementById element in instead of refetching the element each time?

Example, instead of:

document.getElementById("desc").setAttribute("href", "#");
document.getElementById("desc").onclick = function() {...};
document.getElementById("desc").style.textDecoration = "none"
document.getElementById("asc").setAttribute("href", "#");
document.getElementById("asc").onclick = function() {...};
document.getElementById("asc").style.textDecoration = "none"
...

To simply do:

var GlobalElementId = [];
GlobalElementId ["desc"] = document.getElementById("desc");
GlobalElementId ["asc"] = document.getElementById("asc");
GlobalElementId [...] = document.getElementById(...);

GlobalElementId ["desc"].setAttribute("href", "#");
GlobalElementId ["desc"].onclick = function() {...};
GlobalElementId ["desc"].style.textDecoration = "none"
...
TeddyH
  • 221
  • 1
  • 2
  • 3
  • consider jquery for this stuff if possible. – Fahad Nov 11 '09 at 16:19
  • 2
    Does JQuery cache the elementId? If JQuery is just a short hand to reduce typing, that's of no benefit to me on a performance related question. – TeddyH Nov 11 '09 at 16:26
  • 6
    In general jQuery sacrifices speed to rapid application development. – ChaosPandion Nov 11 '09 at 16:26
  • @Fahad, @ChaosPandion - so does JQuery cache the document.getElementByID for each DIV I look up or not? If not, this isn't a viable answer. – TeddyH Nov 11 '09 at 16:28
  • jQuery will not cache the queries you write. See my answer for a very fast way to cache your elements. – ChaosPandion Nov 11 '09 at 16:30
  • JQuery is not just a short hand. Its selector are pretty quick. – Fahad Nov 11 '09 at 16:31
  • @fahad jQuery is slower if he is worried about speed then that would not be a good choice. – corymathews Nov 11 '09 at 16:32
  • 7
    No, it doesn't; in fact it will be slower because you have to do the round trip of adding `#` to the id to get a selector string (and maybe escaping the ID if it contains a `:` or `.`), and then jQuery has to turn that selector back into a call to getElementById and pack the result in a jQuery wrapper... Chaos is right. I'm afraid any question you ask here with JavaScript in it will usually be flooded with blather from the SO “use jQuery! It's so brilliant!!” mafia, regardless of whether jQuery is any help at all in the given situation. – bobince Nov 11 '09 at 16:34
  • Do you know what a `var` is?! – Josh Stodola Nov 11 '09 at 16:46
  • 1
    Is there an answer here @TeddyH that answer your question well enough? If so please pick one! – qodeninja Oct 19 '14 at 07:44
  • Adding properties other than numeric indices to arrays is discouraged. Plus, there is no advantage at all in using an array for this. – doubleOrt Dec 05 '17 at 00:48

12 Answers12

29

So all the "yes" answers were bugging me, so I actually timed this to see if getElementById was slow!

Here are the results (for a page with 10,000 elements on it):

IE8 getElementById: 0.4844 ms
IE8 id array lookup: 0.0062 ms

Chrome getElementById: 0.0039 ms
Chrome id array lookup: 0.0006 ms

Firefox 3.5 was comparable to chrome.

Half a millisecond per function call isn't going to get me to use an array ;) But maybe it's worse on IE6, which I don't have installed.

Here's my script:

<html>
<head>
<script type="text/javascript">
    var numEles = 10000;
    var idx = {};

    function test(){
        generateElements();
        var t0 = (new Date()).getTime();
        var x = selectElementsById();
        var t1 = (new Date()).getTime();
        var time = t1 - t0;
        generateIndex();
        var t2 = (new Date()).getTime();
        var x = selectElementsWithIndex();
        var t3 = (new Date()).getTime();
        var idxTime = t3 - t2;

        var msg = "getElementById time = " + (time / numEles) + " ms (for one call)\n"
            + "Index Time = " + (idxTime/ numEles) + " ms (for one call)";
        alert(msg);
    }

    function generateElements(){
        var d = document.getElementById("mainDiv");
        var str = [];
       for(var i=0;i<numEles;i++){
           str.push("<div id='d_" + i + "' >" + i + "</div>");
        }
        d.innerHTML = str.join('');
    }

    function selectElementsById(){
        var eles = [];
        for(var i=0;i<numEles;i++){
            var id = ((i * 99) % numEles);
            eles.push(document.getElementById("d_" + id));
        }
        return eles;
    }

    function generateIndex(){
        for(var i=0;i<numEles;i++){
            var id = "d_" + i;
           idx[id] = document.getElementById(id);
        }
    }

    function selectElementsWithIndex(){
        var eles = [];
        for(var i=0;i<numEles;i++){
            var id = ((i * 99) % numEles);
            eles.push(idx["d_" + id]);
        }
        return eles;
    }   
</script>
</head>
<body onload="javascript:test();" >
<div id="mainDiv" />
</body>
</html>
Mike Blandford
  • 3,952
  • 4
  • 29
  • 32
10

Since you say "CSS elements" I suspect that a lot of your slow performance is not because of repetitive use of document.getElementById() (which you should avoid anyway) but rather how many times you modify the style object for a given node.

Every single time you change a property on style you force the browser to re-draw that element and possibly many others on the page.

var elem = document.getElementById( 'desc' );
elem.style.textDecoration = "none"; // browser re-draw
elem.style.borderWidth    = "2px";  // browser re-draw
elem.style.paddingBottom  = "5px";  // browser re-draw

Here, the better solution is to use CSS classes and switch or add/remove the class name from the node. This lets you pack in as many style changes you want at the cost of only a single re-draw.

var elem = document.getElementById( 'desc' );
elem.className = "whatever"; // Only one browser re-draw!
Peter Bailey
  • 105,256
  • 31
  • 182
  • 206
  • If you notice in my example, there is only 1 redraw. I'm dynamically creating HREF links and only set the style once. – TeddyH Nov 11 '09 at 16:30
  • 1
    this is still a good point. For whatever reason I had always used classes but I had not thought about this performance penalty before. Good to know. – corymathews Nov 11 '09 at 16:34
  • I was under the impression that most browsers cannot redraw until all scripts stop executing. Not that I am against using classes. – ChaosPandion Nov 11 '09 at 16:35
  • 1
    Browser redraws definitely *can* be a performance problem. Even if you only set the style once per element, that's one browser redraw per element. I've actually optimized this before by removing a table from the dom, modifying a bunch of its cells, and then re-adding it to the dom so there would only be 1 redraw. – Mike Blandford Nov 11 '09 at 16:51
  • In IE, the redraw is immediate. I'm not sure about other browsers – Mike Blandford Nov 11 '09 at 16:52
  • 1
    @TeddyH - Yes I see what you put in your example. But it's just that - an *example*. I never assume that the code someone posts in a question is the entirety of their work that causes them to come here and ask for help. – Peter Bailey Nov 11 '09 at 17:11
3

For me, this would be more appropriate and good for performance :

var desc = document.getElementById("desc");
var asc = document.getElementById("asc");
desc.setAttribute("href","#");
asc.onclick = function() { ... }
...

After reconsidering what ChaosPandion said, I think one way you could do it :

var elements = ["desc", "asc", ...];
for(var i = 0; i < elements.length; i++) {
  GlobalElementId[elements[i]] = document.getElementById(elements[i]);
}
Soufiane Hassou
  • 17,257
  • 2
  • 39
  • 75
  • I want to make this a globalarray so that all functions can use it. Creating individual variables for each CSS element seems overkill to me. So instead, I thought I would just throw it all into a common array bucket. Would this not be appropriate for my use case? – TeddyH Nov 11 '09 at 16:24
  • 1
    Creating individual variables is definitely not the way to go. – ChaosPandion Nov 11 '09 at 16:28
  • well, if you have a lot of variables and if you'll need to do the same thing for many of them (a loop-able "thing") then maybe yes, it's better to use an array. But if there's no possible loop, you'll just make your code bigger with the same performance IMO. – Soufiane Hassou Nov 11 '09 at 16:28
  • Individual variables seems like a terrible idea, hence why I planned to use a hash-array. – TeddyH Nov 11 '09 at 16:33
  • Thanks an interesting idea of doing it. – TeddyH Nov 11 '09 at 16:57
  • It is recommended you use this syntax for arrays ["desc", "asc", ...] – ChaosPandion Nov 11 '09 at 16:59
  • Thanks, I didn't know that ;) Any reason why it is recommended ? – Soufiane Hassou Nov 11 '09 at 17:01
  • Why use i as your indexer variable name? – ChaosPandion Nov 11 '09 at 17:03
  • well i is a common variable name as an indexer, I think it's ok to use it, I could've used 'index' but I don't see the point. – Soufiane Hassou Nov 11 '09 at 17:05
  • LOL sorry, I should never answer a question with a question. What I was trying to say is that it is short and just as understandable. – ChaosPandion Nov 11 '09 at 17:06
2

Yes!

There was a situation not long ago where I was getting poor performance modifying elements. The solution was to build a dictionary like your example. I literally improved performance 1000 times (In IE6 at least).

var elementCache = {};
function buildElementCache() {
    elementCache[id] = {
        element1: document.getElementById(id + "1"),
        element2: document.getElementById(id + "2")
    } 
    // Etc...   
}
ChaosPandion
  • 77,506
  • 18
  • 119
  • 157
1

Depends on the definition of ‘significant’. A GlobalElementId.asc array access is much faster proportionally than a getElementById() call. But getElementById is still very fast compared to most other DOM manipulations your script is likely to be doing, and in all likelihood is only a very very tiny proportion of your script's execution time.

I'd write for readability first, for which Soufiane's answer would seem best. Only if in practice that part of the script was proving to be too slow would I bother starting to think about lookup caches, which add extra complexity (particularly if you start changing those elements at run-time).

Side-note: don't use setAttribute, it's bugged in IE and less readable than just using the DOM Level 1 HTML properties like element.href= '...';.

bobince
  • 528,062
  • 107
  • 651
  • 834
1

The short answer is yes, anytime you can make a Javascript variable or object reference local, it will help with performance.

If you'd like a deeper understanding of scope management and its performance implications on Javascript, the Speed Up Your Javascript tech talk has some really good information. Highly recommended viewing.

Sean McMains
  • 57,907
  • 13
  • 47
  • 54
0

Yes, but using an array is overdoing it.

See Soufiane Hassou´s answer for how to do it.

anddoutoi
  • 9,973
  • 4
  • 29
  • 28
  • I want to make this a globalarray so that all functions can use it. Creating individual variables for each CSS element seems overkill to me. So instead, I thought I would just throw it all into a common array bucket. Would this not be appropriate for my use case? – TeddyH Nov 11 '09 at 16:23
0

Its called object caching and it will boost your script performance.
See at http://www.javascriptkit.com/javatutors/efficientjs.shtml for details.
Also, if you change the CSS often i would suggest using jQuery as suggested by @Fahad.

Dror
  • 7,255
  • 3
  • 38
  • 44
  • Does JQuery automatically do object caching for me? If not, seems like my proposed solution is the best answer, no? – TeddyH Nov 11 '09 at 16:32
  • That link doesn't apply. It's using document.Images not document.getElementById – Mike Blandford Nov 11 '09 at 16:44
  • The idea of not re-searching the DOM each time is what I meant, looking for all images or a specific element by ID illustrates the same idea. – Dror Nov 12 '09 at 07:38
0

No, there would not be a significant performance gain. Your performance problems lie elsewhere. The browser has its own index on element id -> element object.

If you want to find out why your code is slow, it is very important to time it because the slow part is probably not what you'd expect (I've found this out the hard way). You can do so like this:

var t0 = (new Date()).getTime();
var t1 = (new Date()).getTime();
var time = t1 - t0;

Although it's important to note that the accuracy here is 15ms, meaning if something takes 14ms it might show up as 0ms in some browsers.

Here's what your code would look like in jQuery:

$("#desc").attr("href", "#")
    .click(function(){})
    .css("text-decoration", "none");
Mike Blandford
  • 3,952
  • 4
  • 29
  • 32
0

In IE browsers, the answer is YES!

I've done a benchmark (similar to Mike Blandford) and found out that when you call document.getElementById() in IE browser, it traverses the DOM until it finds an element with desired id, instead of keeping an id-to-element map/hashtable. (hideous, I know).

Thus, creating an array, as you offered will be an EXTREME performance improvement.

Omeriko
  • 1,173
  • 11
  • 22
0

Old question, butt... I just created a GPS tracking app that displays distance from market every 2 seconds. I had getElementById in a loop and wanted to test performance against indexing the div. Indexing is at least 10 times faster. Doesn't matter for short 1 time loops... but if you are looping in an app for 20 minutes it is significant, particularly if you are using setInterval or setTimeout.

<html>
<div id="t1">1</div><div id="t2">2</div><div id="t3">3</div>
</html>
<script>
function test_getElement(numCase){
    var object1={}
    for (var cc=0; cc<numCases; cc++){      
        object1["m_" + cc]  = {
                t1:"t1", 
                t2:"t2", 
                t3: "t3"
            }   
    }
    var startTime = performance.now();
    var keys = Object.keys(object1);
    for (var i =0; i<keys.length; i++) {
        document.getElementById("t1").innerText= "t1";//object1[keys[i]].t1;
        document.getElementById("t2").innerText= "t2";//object1[keys[i]].t2;
        document.getElementById("t3").innerText= "t3";//object1[keys[i]].t3;
    }
    var endTime = performance.now();
    return(endTime-startTime);
}

function test_objectSet(numCases){
    var object2={}
    
    for (var cc=0; cc<numCases; cc++){      
        object2["m_" + cc]  = {
                t1: document.getElementById("t1"), 
                t2: document.getElementById("t2"),
                t3: document.getElementById("t3")
            }   
    }
    var startTime = performance.now();
    var keys = Object.keys(object2);
    for (var i =0; i<keys.length; i++) {
        object2[keys[i]].t1 = "t1";
        object2[keys[i]].t2 = "t2";
        object2[keys[i]].t3 = "t3";
    }
    var endTime = performance.now();
    return(endTime-startTime);
}

numCases=100000;
var e = test_getElement(numCases);
var o = test_objectSet(numCases);

alert("GetElementById: " + e + " Object Delaration: " + o);

</script>
Vbudo
  • 405
  • 4
  • 9
0

Here are my results from a serious testing with a highly structured DOM and many DOM elements.

In words:

  • Calling document.getElementById() is very fast.
  • Calling cached DOM elements is much faster, but for most applications this is probably negligable. For js games with many elements it may matter.
  • Calling document.querySelector() is much, much slower than document.getElementById(), if you have a large application/webpage with a highly structured DOM. (Factor 100 to 1000!!)

In numbers (for one of many possible test parameters):

  • hierarchy level: 4

  • number of elements: 4096

  • number of calls: 2048000

  • getElementById(): 0.0004311035156279104 milliseconds

  • querySelector(#): 0.12959199218779394 milliseconds

  • querySelector(.): 0.0694894531250029 milliseconds

  • chached elements: 0.0000039550781293655746 milliseconds

function init() {
  let ids = [];
  let els = [];

  let el0 = document.createElement('div');
  el0.style.display = 'none';
  document.body.appendChild(el0);

  let elementsPerLevel = 4;
  let testLoops = 500;
  let qsTest = 1;
  let qsTestLoops = 5;

  console.log('creating elements ...');

  for (let i = 0; i < elementsPerLevel; i++) {
    let el1 = document.createElement('div');
    el1.id = el1.className = `el-${i}`;
    el0.appendChild(el1);
    for (let j = 0; j < elementsPerLevel; j++) {
      let el2 = document.createElement('div');
      el2.id = el2.className = `el-${i}-${j}`;
      el1.appendChild(el2);
      for (let k = 0; k < elementsPerLevel; k++) {
        let el3 = document.createElement('div');
        el3.id = el3.className = `el-${i}-${j}-${k}`;
        el2.appendChild(el3);
        for (let l = 0; l < elementsPerLevel; l++) {
          let el4 = document.createElement('div');
          el4.id = el4.className = `el-${i}-${j}-${k}-${l}`;
          el3.appendChild(el4);
          for (let m = 0; m < elementsPerLevel; m++) {
            let el5 = document.createElement('div');
            el5.id = el5.className = `el-${i}-${j}-${k}-${l}-${m}`;
            el4.appendChild(el5);
            for (let n = 0; n < elementsPerLevel; n++) {
              let el6 = document.createElement('div');
              el6.id = el6.className = `el-${i}-${j}-${k}-${l}-${m}-${n}`;
              el5.appendChild(el6);
              el6.innerHTML = el6.id;

              ids.push(el6.id);
              els.push(el6);
            }
          }
        }
      }
    }
  }

  let qs1 = ids.map(id => '#' + id);
  let qs2 = ids.map(id => '.' + id);

  let numCalls = testLoops * ids.length;

  console.log('number of elements:', els.length);
  console.log('number of calls:', numCalls);
  console.log('start');

  //now below
  let a;
  let t1 = performance.now();
  for (let i = 0; i < testLoops; i++) ids.forEach(id => a = 1);
  let t2 = performance.now();
  for (let i = 0; i < testLoops; i++) ids.forEach(id => {
    a = 1;
    document.getElementById(id).x = 1;
  });
  let t3 = performance.now();
  let t7 = t3,
    t8 = t3;
  if (qsTest) {
    for (let i = 0; i < qsTestLoops; i++) qs1.forEach(qs => {
      a = 1;
      document.querySelector(qs).x = 1;
    });
    t7 = performance.now();
    for (let i = 0; i < qsTestLoops; i++) qs2.forEach(qs => {
      a = 1;
      document.querySelector(qs).x = 1;
    });
    t8 = performance.now();
  }

  //now above
  let t4 = performance.now();
  for (let i = 0; i < testLoops; i++) els.forEach(el => a = 1);
  let t5 = performance.now();
  for (let i = 0; i < testLoops; i++) els.forEach(el => {
    a = 1;
    el.x = 1;
  });
  let t6 = performance.now();

  let qsFactor = testLoops / qsTestLoops;

  console.log('id times: ref =', t2 - t1, 'test =', t3 - t2, 'total =', t3 - t1, 'qs1 =', (t7 - t3) * qsFactor, 'qs2 =', (t8 - t7) * qsFactor);
  console.log('el times: ref =', t5 - t4, 'test =', t6 - t5, 'total =', t6 - t4);

  console.log("getElementById(): " + ((t3 - t2 - t2 + t1) / numCalls) + " milliseconds.");
  if (qsTest) console.log("querySelector(#): " + (((t7 - t3) * qsFactor - t2 + t1) / numCalls) + " milliseconds.");
  if (qsTest) console.log("querySelector(.): " + (((t8 - t7) * qsFactor - t2 + t1) / numCalls) + " milliseconds.");
  console.log("chached elements: " + ((t6 - t5 - t5 + t4) / numCalls) + " milliseconds.");
}
<body onload="init()"></body>
Oleg Barabanov
  • 2,468
  • 2
  • 8
  • 17
mimi
  • 31
  • 6