So I had to make a recursive version of getElementsByClassName
for a class I'm taking and after I finished I compared it to the native HTMLElement.getElementsByClassName()
.
I found that on average my code is 30% ± 2% less efficient, yet when accounting for non-instant runs I find that I am only 1.5%± 0.5% less efficient. I can't find the native code online anywhere but I am curious, what does it implement to have so many instant results? Does it use closure scope to memorize past inputs or is it something more complicated? My only thought is that I might be losing efficiency from using JQuery but while writing this I realized I indeed didn't need to use JQuery and changed:
var myClassArray = $(me.classList);
var myChildren = $(me.childNodes);
to:
var myClassArray = me.classList;
var myChildren = me.childNodes;
With my new implementation I that on average My code is 11% ± 1% less efficient, yet when accounting for non-instant runs I find that I am only 1.01%± 0.005% less efficient. I am now even more intrigued to know how it achieves so many instant results, as my version is virtually just as efficient when not counting instant runs.
/**
* Recursive version of .getElementsByClassName.
*
* Only elements with ALL of the classNames specified are selected.
*
* @param {String} className the target class name
* @param {HTMLElement} lastMeChild the child of the last recursive element
* @returns {Array} Return array of elements with the targeted className
*/
var getElementsByClassName = function(className, lastMeChild) {
var me;
if (lastMeChild) {
me = lastMeChild;
} else {
me = document.body;
}
var myClassArray = me.classList;
var myChildren = me.childNodes;
var matchingElements = [];
// Detect classes with multiple Strings as class name ex:
var classNames = '';
if (myClassArray !== undefined) {
for (var myClassName = 0; myClassName < myClassArray.length; myClassName ) {
classNames = ' ' myClassArray[myClassName];
}
}
classNames = ' ';
// In order to support multiStr classNames add space at beginning
// and end of className to filter strings conatining same chars
// ex: 'targetClassN ameButNotQuite' or 'ButNotQuitetargetClassN ame'
var uniqClassName = ' ' className ' ';
if (classNames.includes(uniqClassName)) {
// I have the target in my class
matchingElements.push(me);
}
// Check if I have children
if (myChildren !== undefined) {
for (var child = 0; child < myChildren.length; child ) {
matchingElements.push(getElementsByClassName(className, myChildren[child]));
}
}
//Flatten the array so we hae a neat array of depth 1
return matchingElements.flat(Infinity);
};
// Benchmarking:
var avg1 = [];
var avg2 = [];
for (var x = 0; x < 50000; x ) {
var startTime1 = performance.now();
var result = getElementsByClassName('targetC lassName');
var endTime1 = performance.now();
avg1.push(endTime1 - startTime1);
var startTime2 = performance.now();
var expectedNodeList = document.getElementsByClassName('targetC lassName');
var expectedArray = Array.prototype.slice.apply(expectedNodeList);
var endTime2 = performance.now();
avg2.push(endTime2 - startTime2);
}
const average = array => array.reduce((a, b) => a b) / array.length;
var av1 = average(avg1);
var av2 = average(avg2);
console.log('Mine: ' av1 'ms OG: ' av2 'ms');
console.log((av1 < av2) ? ('Mine is ' (Math.round(((av2 / av1) Number.EPSILON) * 1000) / 1000) '% more efficient') : ('OG is ' (Math.round(((av1 / av2) Number.EPSILON) * 1000) / 1000) '% more efficient'));
var av1 = average(avg1.filter(val => val !== 0));
var av2 = average(avg2.filter(val => val !== 0));
console.log('\nNon-Instant: \n\nMine: ' av1 'ms OG: ' av2 'ms');
console.log((av1 < av2) ? ('Mine is ' (Math.round(((av2 / av1) Number.EPSILON) * 1000) / 1000) '% more efficient') : ('OG is ' (Math.round(((av1 / av2) Number.EPSILON) * 1000) / 1000) '% more efficient'));
<div ></div>
<div ></div>
<div>
<div ></div>
</div>
<div>
<div >
<div ></div>
</div>
</div>
<div>
<div></div>
<div>
<div ></div>
</div>
</div>
<div>
<div ></div>
<div ></div>
</div>
<div>
<div >
<div >
<span >Some text for this span.</span>
</div>
</div>
</div>
<div >
<div ></div>
</div>
<div >
<p >
Text for the paragraph tag.
</p>
</div>
<div>
<div >
<div >
<span >Some text for this span.</span>
</div>
</div>
</div>
CodePudding user response:
The getElementsByClassName()
method provided by modern web browsers is likely implemented with native code, which is optimized for performance. This means that it is written in a low-level programming language such as C , which is compiled to machine code and runs directly on the computer's hardware. This is in contrast to JavaScript, which is a higher-level language that runs on top of the web browser's JavaScript engine.
In terms of implementation, it is likely that the browser's getElementsByClassName()
method uses a combination of techniques to optimize its performance. One of these techniques may be a pre-built index of elements that have a given class name, which allows for fast lookups based on class name. Another technique that may be used is caching, where the method remembers the results of previous calls with the same input, so that it does not need to traverse the DOM tree again for the same input.
It's important to note that different Browsers might implement the native getElementsByClassName
differently. This is why it's generally faster than your own implementation.
Also, using jQuery generally has a performance penalty over using native methods. It's good that you removed it from your implementation. It's also worth mentioning that, if you are doing a performance test, you should make sure that you test with a large dataset and run the test multiple times to get an accurate representation of the performance difference.