以下の内容はhttps://let.blog.jp/tag/getElementsByTagNameより取得しました。


getElementsByXXX の速度(続き)
前記事から続きます
HTMLCollection という特性上 遅そうに思ってましたが速かったです
プロパティにアクセスするたびに 検索していたらこの速度にはならないと思うので 工夫はされてるはずです
思いつくのだと DOM の更新時に変更が内部的に通知され その変更が HTMLCollection の監視対象であれば内部状態を更新している とかでしょうか?
それだと有効な HTMLCollection があれば DOM の更新速度が遅くなりそうです

試しに数百の HTMLCollection を作成してから body の子孫要素を大きく書き換えてみました
そのときの DOM 更新にかかった時間と HTMLCollection を作らない場合の DOM 更新にかかった時間を比べてみました

結果は差があるとは言えないくらいのものでした
どのタイミングで処理されてるのでしょうね
getElementsByXXX の速度
querySelector より getElementById のほうが速いという話題を目にしました
たしかにそうですね
でも普通のウェブページで 1 回のイベントあたりで呼び出す回数程度で体感できる差はまずないのでどっちでもいいと思います
という感じで流そうとしてましたが 気になったところがありました

querySelectorAll と getElementsByTagName (getElementsByClassName) の比較です
これも getElementsByTagName のほうが高速とありましたが 本当にそうなんでしょうか?
これらはどちらも複数の要素を得られますが 重要な違いがあります

querySelectorAll で得られるものは NodeList で だいたい配列みたいなものです
querySelectorAll を実行した時点で一致するものが入っていて ツリー構造に変化があっても NodeList は変化しません

getElementsByTagName で得られるのは HTMLCollection で Proxy オブジェクトみたいなものです
getElementsByTagName を実行した時点ではなく HTMLCollection のプロパティにアクセスした時点で一致するものです
常にその時点の状態に更新されています

HTMLCollection の動作的に getElementsByTagName を行った時点で検索する必要はありません
検索条件(ByTagName ならタグ名)を内部で保持するオブジェクトを作るだけでいいです
なので getElementsByTagName を行うだけなら 実際に検索する querySelectorAll よりも圧倒的に速いでしょう
ですが 実際に使う場合は検索した結果を取り出します
各要素にアクセスまでしたら getElementsByTagName のほうが速いとは言えないような気もします

ということで試してみました

const caseGetElements = () => {
const elems = document.getElementsByTagName("div")
const items = []
for (let i = 0; i < elems.length; i++) {
items.push(elems[i])
}
return items
}

const caseQuerySelector = () => {
const elems = document.querySelectorAll("div")
const items = []
for (let i = 0; i < elems.length; i++) {
items.push(elems[i])
}
return items
}

const measure = (fns, rep, mrep, cut) => {
const cases = fns.map(fn => ({ fn, results: [] }))

for (let i = 0; i < mrep; i++) {
for (const { fn, results } of cases) {
const start = performance.now()
for (let j = 0; j < rep; j++) {
fn()
}
const end = performance.now()
results.push(end - start)
}
}

return cases.map(({ results }) => {
const targets = results.toSorted((a, b) => a - b)
.slice(cut)
.slice(0, -cut)

const round = (x, d = 3) => +x.toFixed(d)
const len = targets.length
const min = round(targets[0])
const max = round(targets.at(-1))
const avg = round(targets.reduce((a, b) => a + b, 0) / len)
const med = round(len % 2 ? targets[~~(len / 2)] : (targets[(len / 2) - 1] + targets[len / 2]) / 2)

return { len, min, max, avg, med, results, targets }
})
}

console.log(measure([caseGetElements, caseQuerySelector], 100, 100, 10))

div タグを検索します
検索結果を for 文でひとつずつ取り出して配列に追加しています
この処理を 100 回したときにかかった時間を計測します
ブラウザでは最適化などで実行で速度にばらつきが出るので 100 回計測して速い遅いの両端 10 回分ずつを除外した 80 件分で 最大・最小・平均・中央値を出してます

結果はページによって違いが大きいです

このブログトップだと

  getElementsByTagName
    6 ~ 7.1ms, a: 6.47ms, m: 6.45ms

  querySelectorAll
    9.2 ~ 11ms, a: 9.646ms, m: 9.6ms

  div: 446, *: 4036

getElementsByTagName のほうが速いようです
1.5 倍くらいです

メインの方のブログでは

  getElementsByTagName
    5.1 ~ 5.7ms, a: 5.27ms, m: 5.3ms

  querySelectorAll
    18.2 ~ 19.7ms, a: 18.48ms, m: 18.3ms

  div: 374, *:4858

更に差が出ました
3 倍以上です

MDN のページでは近い結果になりました
いくつか試した限りではこのページが一番近かったです
https://developer.mozilla.org/ja/docs/Web/API/Performance/now

  getElementsByTagName
    2.5 ~ 3ms, a: 2.624ms, m: 2.6ms

  querySelectorAll
    2.6 ~ 3.4ms, a: 2.925ms, m: 2.9ms

  div: 177, *: 940

10% ちょっとくらいしか差がありません

Google の検索結果ページでは逆の結果でした

  getElementsByTagName
    15.3 ~ 17.6ms, a: 15.74ms, m: 15.6ms

  querySelectorAll
    11.9 ~ 14ms, a: 12.559ms, m: 12.3ms

  div: 1149, *: 2069

querySelectorAll のほうが速いです

検索対象のタグ数と一致するタグ数で考えると
検索対象のタグ数が多いほど querySelectorAll が遅くなり
一致するタグが多いほど getElementsByTagName が遅くなるようです

内部の最適化次第で変化しそうなものなので ブラウザやバージョンでも変わりそうですね
この結果は Chrome 117 のものです



ところで 各要素にアクセスせず getElementsByTagName と querySelectorAll を呼び出しただけの場合も一応計測しました

const caseGetElements = () => {
return document.getElementsByTagName("div")
}

const caseQuerySelector = () => {
return document.querySelectorAll("div")
}

結果はこうなりました
平均と中央値だけ載せてます
それぞれ上が getElementsByTagName で下が querySelectorAll のものです

このブログのトップ
  a: 0.005, m: 0
  a: 5.705, m: 5.5

メインのブログのトップ
  a: 0.007, m: 0
  a: 15.49, m: 15.3

MDN のページ
  a: 0.005, m: 0
  a: 1.113, m: 1.1

Google の検索結果
  a: 0.006, m: 0
  a: 4.411, m: 4.3

予想どおりですが圧倒的な差ですね



以上の内容はhttps://let.blog.jp/tag/getElementsByTagNameより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14