こんにちは。なべです。

入社半年の新人 ですが、このような場をいただきましたので、入社してすぐに取り組んだInternetExplorer(以下、IE) 8のjQueryのパフォーマンス対策について書いてみたいと思います。

なぜIE8か?

このブログにたどり着くような方はHTML5をいじってみたり、普段使用するブラウザもFirefoxやChromeという場合が多いと思います。
そんな中、この記事のタイトルを見て、なぜ今さらIE8・・・と思ったのではないでしょうか?

というわけで、まずは、なぜ今IE8のパフォーマンス対策なのかを説明したいと思います。
 
sd_brws_share
右のグラフは、スーパーデリバリーにおける訪問者のブラウザのシェアを簡略化したグラフです。
グラフにもあるようにIE8はスーパーデリバリーではおよそ15.4%のユーザーが利用しています。
※2013/07時点のデータです。

IE8が多い理由としては以下のことが考えられます。
・スーパーデリバリーはBtoBサイトで、出展企業様や小売店様が仕事ととして利用されるため、会社やお店などにある仕事用のPCから利用されている。
・その場合、やはりWindowsが多く、さらに購入時にプレインストールされているブラウザを利用するケースが多いためIEが多い。
・会社内のPCは家庭にくらべて新陳代謝が緩やかなので、数年内にリリースされたバージョンが混在する。(IE8,9,10がほぼ同じ割合)
ちなみにIE6もまだ1%強使われています。
 
その前提に立つと、今後、Windows XPのサポート切れに伴って企業内で利用するPCの買い替えが進み、Windows8のシェアが上がってくるにつれて、IE10のシェアが増えてくることが予想されますが、それまでしばらくはIE8は主要ブラウザとして扱う必要があります。

さて、IE8サポートの重要性がわかったところで本題に入りたいと思います。

問題

「IE8のjQueryのパフォーマンス対策」というのは、IE8でスーパーデリバリーの商品一覧ページを開くと、画面が利用できるようになるまでに時間がかかるという問題への対応でした。
プロファイラなどで調査した結果、原因は$(".hoge")のようにクラスをセレクタに指定してjQueryオブジェクトを操作している部分で処理が重くなり、時間がかかっていることがわかりました。

それはなぜでしょうか? 

この現象の説明の前に、jQueryの仕組みを少しお話しておきます。

jQueryはブラウザ間の違いを吸収して共通のインターフェースを提供してくれるJavaScriptのライブラリです。
jQueryはどのブラウザでも同じように利用することができますが、内部的にはブラウザによって処理を分岐し、微妙な違いを吸収して、ブラウザの機能を呼び出したり、jQueryで実装したりして、ドロドロした部分を隠し、我々に使いやすいようにしてくれています。

この実装の違いに落とし穴があります。
ブラウザの機能を使える場合には、ネイティブメソッドを呼び出すことができますが、jQueryで実装となった場合は、ブラウザ上で動くJavaScriptの処理になります。
当然JavaScriptよりもネイティブメソッドのほうが速いためここでパフォーマンスの差がでます。
よって、できるだけjQueryの独自実装部分に入らないように実装することがパフォーマンス向上の一つの施策になります。

さて、IE8の話に戻ります。
ここまでの説明で想像がついているかと思いますが、IE8でクラスをセレクタに指定した処理が重いのは、IE8まではクラスセレクタで要素を取得する機能がブラウザの機能として提供されていない(=ネイティブメソッドにない)ためです。
これはJavaScriptのコンソールなどで以下をそれぞれ打つと確認することができます。
document.getElementById("aaa")
document.getElementsByTagName("aaa")
document.getElementsByClassName("aaa")
よって、jQueryではIE8でクラスをセレクタに指定された場合、JavaScriptにより、対象となる全要素をループ処理で走査するため遅くなっています。

ちなみに、このjQueryの検索ロジックのライブラリをSizzleといいます。
SizzleはjQueryの内部で使われていて、意識して利用することはほとんどないはずですが、github上で公開されているオープンソースのプロジェクトなので、より深く知りたい場合にはこちらをご覧ください。

対策

クラスセレクタを指定してjQueryオブジェクトを取得すると検索処理が実行されて重くなるわけですから、対策としては、クラスをセレクタに指定してノードを検索する処理をできるだけ避ける必要があります。
そこで、以下の3つの対策が考えられます。
     
1. そもそもクラスセレクタを使わない
2. クラスセレクタによる検索対象を減らす
3. クラスセレクタによる検索回数を減らす

それでは個々の対策を説明していきたいと思います。

1. そもそもクラスセレクタを使わない

クラスセレクタを使わずにID("#xxx")で指定できるところはIDで指定します。
IDを指定することにより、ネイティブメソッドでサポートされているgetElementByIdが使用されますので、これだけで速度は改善します。
注意しなければならないのは、IDはDocument上で一意でなければならない点です。
この制約が守れるのであれば、IDに置き換えるのが良いでしょう。

2. クラスセレクタによる検索対象を減らす

とはいえ、IDに単純に置き換えられるところばかりではありません。むしろ、少ないでしょう。
だからといって、
jQueryを使う上で、クラスを使って複数の要素をまとめて操作する機能を使わないというのは考えられません。
ですので、以降はクラスセレクタを使いつつ負荷を最小限にする手段をご紹介します。
 
まずはクラスセレクタで検索する対象を減らし、走査にかかる負荷を減らすことを考えます。
例えばbarクラスを操作したい場合、     
$(".bar")
と書くと、全ての要素が検索対象になってしまいます。
そこで、barクラスの前にかならずIDをもつ親要素があるならば、そこを基点にして     
$("#foo").find(".bar")
と指定します。こうすることで、ID(foo)の以下にある要素だけを検索対象にすることができます。
さらに必ず親要素がID:fooであることがわかっているなら、     
$("#foo").children(".bar")
と指定します。こうすることで、子要素だけに対象を絞り、孫要素より下の階層を検索対象から外すことができます。
ほかにも、next(兄弟要素の次)やsiblings(兄弟要素)、parent(親要素)など基点を中心に周辺を指定できますので活用すると良いと思います。

次に、タグ名を指定することでも負荷の軽減になります。
getElementsByTagNameもネイティブメソッドに存在するため、いきなりクラスを指定するよりも高速になります。    
$("div.bar")
と.いうようにタグと一緒にクラスを指定したり    
$("li").find(".bar")
のようにタグで親になる要素を絞ってから、その下のクラスを指定することで対象を絞ることができます。

このように、できるだけ検索対象の母数を絞ることで、負荷を軽減します。

3. クラスセレクタによる検索回数を減らす

つぎに、クラスセレクタによる重い検索処理を何度も実行して負荷が上がっている場合があります。
例えば以下のコードはどうでしょうか。
$(".bar").css("color","#fff");
$(".bar").css("border","thin solid #000")
$(".bar").width(100);
負荷の高い$(".bar")の検索を3度も実施しています。
この場合は検索をした結果を一度変数に格納し使いまわすことを考えます。
var bar=$(".bar");
bar.css("color","#fff");
bar.css("border","thin solid #000");
bar.width(100);
このように変更することで検索を1回にすることができます。

ちなみに、今回の例のように設定系の処理だけであれば、ドット(.)でつなげて以下のようにも書くことができます。       
$(".bar").css("color","#fff").css("border","thin solid #000").width(100);

以上が3つの対策になります。

まとめ

スーパーデリバリーでは、これら3つの対策をすることで「IE8でjQueryが重い」現象に対応し、パフォーマンス問題を改善することができました。
もし、いまお困りであれば試してみてください!
 
まとめのまとめとして、今回の対策からの学びを、本質的な観点からまとめたいと思います。

今回最も大きな原因は、IE8の機能不足によるものでした。
ただし、使用するライブラリや実行環境の問題というのは多くの場合制御不能なので、”特徴”と捉えてうまく付き合うしかありません。
今回の現象も、「重い」という問題になる前に特徴を把握して、対策をしておけば、顕在化することなく防げていたと思います。

ただ、そもそも、「対策」にあげている、2.検索対象を減らしたり、3.検索回数を減らすといった対策は、通常のプログラムを書く場合や、SQLを考える場合などでも考慮すべき点です。
ひと昔前はJavaScriptというとプログラミング言語というよりはHTMLの一部みたいに考えていた頃もありましたが、改めてプログラミング言語だということを意識して、設計・実装を行うことが必要です。
これらを意識していれば、もし仮に特徴を知らなかったとしても、パフォーマンスが問題にならなかったかもしれません。

また、パフォーマンスの改善の効果の他に、検索回数を減らすために要素の取得処理をまとめ、複数箇所に分散させないことで、修正箇所が絞られ、スクリプト言語にありがちなスペル違いや修正漏れを防ぐことができ、メンテナンス性も改善できます。

日頃からシンプルで保守性の高いコードを心がけたいですね!
Don't repeat yourself!

最後に

スーパーデリバリーのTOPページを2013年7月3日にリニューアルしています。
デザイナーとプログラマのこだわりがぎっしり詰まったNewTOPページをぜひご覧ください
担当したログイン後のスライドショーはスーパーデリバリーの会員様だけにしかお見せできないのが少し残念ですが、、、