HTML DOM的getElementsByClassName()为何如此高效?
递归版
getElementsByClassName的性能分析与原生实现疑问 我为课程实现了递归版本的getElementsByClassName,完成后与原生HTMLElement.getElementsByClassName()做了性能对比:
- 最初依赖jQuery的版本:自定义代码平均效率低30% ± 2%,排除瞬时运行(耗时为0的情况)后仅低1.5%± 0.5%
- 移除jQuery依赖后(将
$(me.classList)改为me.classList,$(me.childNodes)改为me.childNodes):自定义代码平均效率低11% ± 1%,排除瞬时运行后仅低1.01%± 0.005%
我找不到原生方法的实现代码,好奇原生方法为何会出现大量瞬时运行结果,它的底层实现机制是什么?
实现代码
JavaScript代码
/** * 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: class="red apple" 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'));
HTML测试结构
<div class="targetC lassName"></div> <div class="otherClassName targetC lassName"></div> <div> <div class="targetC lassName"></div> </div> <div> <div class="targetC lassName"> <div class="targetC lassName"></div> </div> </div> <div> <div></div> <div> <div class="targetC lassName"></div> </div> </div> <div> <div class="targetC lassName"></div> <div class="targetC lassName"></div> </div> <div> <div class="someOtherDiv"> <div class="targetC lassNameButNotQuite innerDiv"> <span class="targetC lassName">Some text for this span.</span> </div> </div> </div> <div class="container"> <div class="targetC lassName randomClass"></div> </div> <div class="paragraph text targetC lassName"> <p class="intro targetC lassName"> Text for the paragraph tag. </p> </div> <div> <div class="someOtherDiv"> <div class="ButNotQuitetargetC lassName innerDiv"> <span class="targetC lassName">Some text for this span.</span> </div> </div> </div>
原生getElementsByClassName的底层机制解析
1. 预构建的DOM索引表
浏览器在解析DOM时,会为元素的类名维护哈希表或类似的高效索引结构,而非每次调用时遍历整个DOM树。调用getElementsByClassName时,浏览器直接从索引中查询匹配元素,不需要像递归实现那样深度遍历节点,因此很多情况下能瞬间返回结果。
2. 惰性求值的动态集合
原生方法返回的是动态NodeList,它并不是立即收集所有匹配元素并生成数组,而是返回一个指向索引的引用。你的测试代码中用Array.prototype.slice.apply()转数组时才会触发元素收集,但浏览器的索引优化依然让这个过程极快。而递归实现每次调用都要完整遍历DOM、收集元素并扁平化数组,这部分额外开销无法避免。
3. 编译型语言的底层优势
原生DOM API用C++等编译型语言实现,直接运行在浏览器渲染引擎中(如Chrome的Blink、Firefox的Gecko):
- 没有JS引擎的解释/编译开销,内存操作和遍历逻辑更高效
- 可直接访问DOM底层数据结构,无需通过JS DOM接口间接调用,减少了桥接层的性能损耗
4. 瞬时运行的本质
那些耗时为0的情况,是因为浏览器的索引查询和惰性集合初始化操作极快,快到performance.now()(精度通常为微秒级)无法检测到时间差,实际操作可能仅需纳秒级完成。
内容的提问来源于stack exchange,提问作者Jake
相关产品推荐
相关产品推荐

