DOM は XML および HTML ドキュメントを操作するためのアプリケーション プログラミング インターフェイスであり、DOM を操作するためにスクリプトを使用すると非常にコストがかかることはわかっています。 DOM と JavaScript (ここでは ECMScript) がそれぞれ 1 つの島であり、ECMAScript が DOM にアクセスするたびに、この橋を通過して「橋の通行料」を支払う必要があると想像してください。 DOM へのアクセス回数が増えるほど、コストが高くなります。したがって、推奨されるアプローチは、できるだけ少ない数の橋を渡り、ECMAScript アイランドにとどまるようにすることです。 DOM インターフェースを使用しないことは不可能ですが、プログラムの効率を向上するにはどうすればよいでしょうか?
1. DOM へのアクセスと変更
DOM 要素へのアクセスにはコストがかかります (ご存知のとおり、「料金」がかかります)。また、要素の変更には、ブラウザがページの幾何学的変更 (リフローと再描画) を再計算する必要があるため、さらにコストがかかります。
もちろん、最悪のシナリオはループ内の要素にアクセスまたは変更することです。次の 2 つのコードを見てください:
var times = 15000; // code1 console.time(1); for(var i = 0; i < times; i++) { document.getElementById('myDiv1').innerHTML += 'a'; } console.timeEnd(1); // code2 console.time(2); var str = ''; for(var i = 0; i < times; i++) { str += 'a'; } document.getElementById('myDiv2').innerHTML = str; console.timeEnd(2);
その結果、最初の実行時間は 2 回目の実行時間よりも 1,000 倍長くなりました。 (クロームバージョン44.0.2403.130m)
1: 2846.700ms 2: 1.046ms
コードの最初の部分の問題は、各ループ反復で要素が 2 回アクセスされることです。1 回目は innerHTML の値を読み取るため、もう 1 回目は値を書き換えるためです。つまり、ループがブリッジを通過するたびに ( re-Rowingとredrawについては次の記事で説明します)!この結果は、DOM へのアクセス回数が増えるほど、コードの実行が遅くなることを明確に示しています。そのため、削減できるDOMアクセス数を極力減らし、ECMAScript側に処理を任せることになります。
2. HTML コレクションと DOM の走査
DOM の操作でエネルギーを消費するもう 1 つのポイントは、DOM の走査です。一般に、getElementsByTagName() や document.links などを使用して HTML のコレクションを収集します。これについては誰もがよく知っていると思います。コレクションの結果は、リアルタイムで「ライブ状態」で存在する配列のようなコレクションです。つまり、基になるドキュメント オブジェクトが更新されると自動的に更新されます。どう言えばいいでしょうか?栗をあげるのはとても簡単です:
<body> <ul id='fruit'> <li> apple </li> <li> orange </li> <li> banana </li> </ul> </body> <script type="text/javascript"> var lis = document.getElementsByTagName('li'); var peach = document.createElement('li'); peach.innerHTML = 'peach'; document.getElementById('fruit').appendChild(peach); console.log(lis.length); // 4 </script>
そして、これが非効率の原因です。配列の最適化操作と同じように、長さ変数をキャッシュしても問題ありません (コレクションの長さの読み取りは、毎回クエリを実行する必要があるため、通常の配列の長さを読み取るよりもはるかに時間がかかります)。 >
console.time(0); var lis0 = document.getElementsByTagName('li'); var str0 = ''; for(var i = 0; i < lis0.length; i++) { str0 += lis0[i].innerHTML; } console.timeEnd(0); console.time(1); var lis1 = document.getElementsByTagName('li'); var str1 = ''; for(var i = 0, len = lis1.length; i < len; i++) { str1 += lis1[i].innerHTML; } console.timeEnd(1);
0: 0.974ms 1: 0.664ms
「High-Performance JavaScript」では、別の最適化戦略を提案しています。「配列の走査はコレクションの走査よりも速いため、最初にコレクションの要素を配列にコピーすると、そのプロパティへのアクセスがより速くなります。」と述べています。このパターンはあまりよく分かりませんでしたので、気にしないでください。テスト コードは次のとおりです。 (ご質問がございましたら、お気軽にご相談ください)
console.time(1); var lis1 = document.getElementsByTagName('li'); var str1 = ''; for(var i = 0, len = lis1.length; i < len; i++) { str1 += lis1[i].innerHTML; } console.timeEnd(1); console.time(2); var lis2 = document.getElementsByTagName('li'); var a = []; for(var i = 0, len = lis2.length; i < len; i++) a[i] = lis2[i]; var str2 = ''; for(var i = 0, len = a.length; i < len; i++) { str2 += a[i].innerHTML; } console.timeEnd(2);
console.time(1); var lis1 = document.getElementsByTagName('li'); console.timeEnd(1); console.time(2); var lis2 = document.querySelectorAll('li'); console.timeEnd(2); // 1: 0.038ms // 2: 3.957ms
var elements = document.querySelectorAll('#menu a'); var elements = document.querySelectorAll('div.warning, div.notice');