たしかにそうですね
でも普通のウェブページで 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
予想どおりですが圧倒的な差ですね