You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.02 20:30:43