JavaScript: document.getElementById slow performance?

Viewed 14275

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"
...
12 Answers

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>

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>

Related