JavaScript: document.getElementById slow performance?
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.textDe开发者_如何学Pythoncoration = "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"
...
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>
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!
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]);
}
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...
}
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= '...';
.
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.
Yes, but using an array is overdoing it.
See Soufiane Hassou´s answer for how to do it.
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.
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");
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.
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>
精彩评论