JavaScript: document.getElementById slow performance?
Asked Answered
G

12

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"
...
Gutter answered 11/11, 2009 at 16:17 Comment(11)
consider jquery for this stuff if possible.Silverfish
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.Gutter
In general jQuery sacrifices speed to rapid application development.Amblyopia
@Fahad, @Amblyopia - so does JQuery cache the document.getElementByID for each DIV I look up or not? If not, this isn't a viable answer.Gutter
jQuery will not cache the queries you write. See my answer for a very fast way to cache your elements.Amblyopia
JQuery is not just a short hand. Its selector are pretty quick.Silverfish
@fahad jQuery is slower if he is worried about speed then that would not be a good choice.Brassbound
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.Typical
Do you know what a var is?!Fazio
Is there an answer here @Gutter that answer your question well enough? If so please pick one!Roxieroxine
Adding properties other than numeric indices to arrays is discouraged. Plus, there is no advantage at all in using an array for this.Irs
C
30

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>
Callida answered 11/11, 2009 at 17:43 Comment(7)
Seriously, why is IE so slow! I don't even want to think about how slow IE6 is.Amblyopia
I've just had a situation in a huge form where a UI operation went from 2m40s to 20s (ok, it is too big, ok?) and I was about to call it a day. This optimization made it 0.7s! Now I'm done! ;-)Saccharoid
anyway you can post this to jsperf? Might be good for other people who find your answerRoxieroxine
@PeterV.Mørch Which optimization ? The array one ?Irs
@Taurus: I don't remember. That was 6 years ago.Saccharoid
@PeterV.Mørch Ohhh, terrible.Irs
For benchmarks use: developer.mozilla.org/en-US/docs/Web/API/Performance/now or jsbench.meMisconceive
G
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!
Goldwin answered 11/11, 2009 at 16:27 Comment(6)
If you notice in my example, there is only 1 redraw. I'm dynamically creating HREF links and only set the style once.Gutter
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.Brassbound
I was under the impression that most browsers cannot redraw until all scripts stop executing. Not that I am against using classes.Amblyopia
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.Callida
In IE, the redraw is immediate. I'm not sure about other browsersCallida
@Gutter - 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.Goldwin
L
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]);
}
Looming answered 11/11, 2009 at 16:19 Comment(10)
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?Gutter
Creating individual variables is definitely not the way to go.Amblyopia
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.Looming
Individual variables seems like a terrible idea, hence why I planned to use a hash-array.Gutter
Thanks an interesting idea of doing it.Gutter
It is recommended you use this syntax for arrays ["desc", "asc", ...]Amblyopia
Thanks, I didn't know that ;) Any reason why it is recommended ?Looming
Why use i as your indexer variable name?Amblyopia
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.Looming
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.Amblyopia
A
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...   
}
Amblyopia answered 11/11, 2009 at 16:19 Comment(0)
T
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= '...';.

Typical answered 11/11, 2009 at 16:28 Comment(0)
D
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.

Darreldarrell answered 11/11, 2009 at 16:51 Comment(0)
O
0

Yes, but using an array is overdoing it.

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

Omland answered 11/11, 2009 at 16:20 Comment(1)
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?Gutter
I
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.

Invade answered 11/11, 2009 at 16:30 Comment(3)
Does JQuery automatically do object caching for me? If not, seems like my proposed solution is the best answer, no?Gutter
That link doesn't apply. It's using document.Images not document.getElementByIdCallida
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.Invade
C
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");
Callida answered 11/11, 2009 at 16:42 Comment(0)
W
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.

Wun answered 22/6, 2011 at 11:51 Comment(0)
V
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>
Vigilante answered 13/4, 2022 at 21:47 Comment(0)
K
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>
Katheykathi answered 31/8, 2022 at 8:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.