オフタイムに趣味で閲覧しているとあるサイトにおいて、表示完了までに約8秒(Firefoxを使用)の時間を要するサイトがありました。仕事ではないのであまり気にかけてはいなかったのですが、他の閲覧者の方から「IEで見ると40秒以上かかるのですが...」という話が上がり、フロントエンド・エンジニアとして少し調べてみるとにしました。
時間がかかっているのはJavaScript
実際にIEで閲覧してみると確かに非常に長い時間待たされます。ただ画面には「読み込み中」のテキストが表示されているので、「もしかするとブラウザでの処理に時間がかかっているのか」と考えました。
そこでIEの開発者向けツールでプロファイリングをしてみると、長い時間待っているはスクリプトの処理が続いていることが分かりました。(赤い棒が長時間にわたり伸びています。)
プロファイリング結果からコードの問題の特定
ブラウザを仕事の際に普段使用しているChromeに戻し、再度プロファイリングを取ってみました。結果、下記のようなデータを得ることができました。
画像では長時間処理に時間がかかっている部分にフォーカスしています。XMLHTTPRequestの処理に時間がかかっているのですが、さらに探っていくとsuccess
内の処理に問題がありそうなことが分かります。JavaScriptのファイル名と行数も表示されているので、示された部分(下記サンプルコード)を中心に見ていくことにします。
$.ajax({
url: "/xml/entries.xml",
dataType: "xml",
success: function(xml) {
var layer = $('<div id="news" />');
var ul = $('<ul />');
$(xml).find("item").each(function(i) {
var tag = $('<li class="item"><span class="date">' + $(this).find("entryDate").text() + '</span><spa class="title">' + $(this).find("entryTitle").text() + '</span></li>');
layer.append(ul.append(tag));
});
layer.append(ul);
}
});
先の画像には表示されていないのですが、開発者ツール内のBottom Upタブの先頭には「appendChild」や「Recalculate Style」が表示されていました。以上のことから問題はlayer.append(ul.append(tag));
にあると推測します。もっともループ内で.append()
を実行するとパフォーマンスに影響を与えることは知っていたので、ここが問題だと確信していました。
※jQueryのコードを読めば分かるのですが、.append()
の中でJavaScriptネイティブの.appendChild()
をコールしています。
.append()
処理の改善
そこでループ内で.append()
を実行することをやめ、下記サンプルコードのようにループ内ではHTMLの文字列を連結しループ外で.append()
を実行するようにしたところ、パフォーマンスは大幅に改善。Chromeで約7.5秒要していた処理が約2秒に、またIEでは40秒以上要していた処理が約3秒で完了するようになりました。
$.ajax({
url: "/xml/entries.xml",
dataType: "xml",
success: function(xml) {
var layer = $('<div id="news" />');
var ul = $('<ul />');
var lists = '';
$(xml).find("item").each(function(i) {
var tag = $('<li class="item"><span class="date">' + $(this).find("entryDate").text() + '</span><spa class="title">' + $(this).find("entryTitle").text() + '</span></li>');
lists += html;
});
ul.append($(lists));
layer.append(ul);
}
});
ただ、これでもBottom Upタブの先頭には「Recalculate Style」が出てきます。ふと開発者ツールのElementsタブでul要素を見てみると、非常に多くのli要素が追加されていることに気付きました。XMLには必要以上の記事が出力されていたようです。
そこで明らかに表示されない記事をDOMに追加しないように改善したところ、パフォーマンスはさらに改善しChromeでは一瞬で表示が完了するようになりました。まだ「Forced reflow」の警告が出ていたりコードに気になる点がありますが、本記事ではここまでの紹介とさせて頂きます。
長期間の運用に伴って落ちた表示パフォーマンス
この事例では、サイト運用開始当初は表示パフォーマンスの問題はなかったと推測します。恐らく新規構築で記事数が少ないために、表示パフォーマンス上良くないコードであったとしても処理がすぐに終わっていたのだろうと考えられます。
最初に負荷テストを実行して問題がないか確かめることもできますしコーディングのベストプラクティスを適用することで避けることも可能ですが、運用開始後定期的に表示パフォーマンスに問題はないかチェックすることも重要ではないかと考えます。