Reputation: 221
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"
...
Upvotes: 22
Views: 16508
Reputation: 31
Here are my results from a serious testing with a highly structured DOM and many DOM elements.
In words:
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>
Upvotes: 0
Reputation: 370
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>
Upvotes: 0
Reputation: 1185
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.
Upvotes: 0
Reputation: 4012
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>
Upvotes: 30
Reputation: 17750
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]);
}
Upvotes: 3
Reputation: 59203
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.
Upvotes: 1
Reputation: 4012
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");
Upvotes: 0
Reputation: 7303
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.
Upvotes: 0
Reputation: 536389
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= '...';
.
Upvotes: 1
Reputation: 105888
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!
Upvotes: 10
Reputation: 10111
Yes, but using an array is overdoing it.
See Soufiane Hassou´s answer for how to do it.
Upvotes: 0
Reputation: 78262
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...
}
Upvotes: 2