console.lealog();

@leader22のWeb系に関する勉強めもブログですのだ

jQueryとSizzleの関係について

事の発端。

  • jQueryの使わない機能があまりに多いのでカスタムビルドすることにした
  • Sizzleも外せることを知る(容量がgzipで20KB分くらい減る)
  • 外すと何が変わるか気になる <- いまここ

ってなモチベーションで調べた一連の内容です。

jQueryとSizzleの関係

あらためて一応。
jQueryといえば」な以下のようなコード。

var $hoge = $('#hoge'); // こういうのとか
var $fuga = $('.fuga'); // こういうのとか
$hoge.find('.foo');     // こういうのとか
$hoge.text();           // 実はこういうのも

そう、いわゆるこのセレクタの実態がSizzleなのです。
正確にはもう少し他のメソッドにも関係があります。

jquery/sizzle

で、そのSizzleがjQueryの中でどういう動きをしてるかを調べてみるエントリでございます。

コードを読む

それが一番はやかろうということで、jQueryの2.xの最新版を読んでいく。

jquery/jquery at 2.1-stable

jquery/src/core/init.js

jquery/init.js at 2.1-stable · jquery/jquery

コレがいわゆる本丸、$(selector, context) のところ。
contextはさておき、selectorがどう判別されていって動いてるかをざっくり書くと。

selectorが文字列なら
  • '<div>' みたいな文字列なら

div要素を作ってそれをラップした$オブジェクトにしてreturn

  • '#hoge' みたいな文字列なら

document.getElementById('hoge')したものをラップした$オブジェクトにしてreturn

  • それ以外は$.fn.findしたものをreturn

これが実は重要なので、[*1]で後述。

selectorがDOMElementなら
  • その要素をラップした$オブジェクトにしてreturn
そのほかselectorが関数の場合などなど

今回は気にしないので割愛。

[*1] $.fn.find()とは

上述した流れで、$('#id')以外のだいたいのセレクタがココにたどり着くことになるのですが、
これが実はそのまんまSizzleに渡ります。

idが最速

この時点で言えることですね。
そもそもSizzleに渡らないので、以降の処理や判定がない分速いです。

つまり速さを追い求めてるなら、'#hoge'的な文字列を渡す使い方しかしちゃダメってことです!!!1

冒頭に立ち戻る

で、今回はSizzleの有無による差分が気になるエントリなので、2パターンについてそれぞれ追っていきます。
ちなみに、さっきのリポジトリでもその差分はさくっと見れます。

jquery/selector-native.js at 2.1-stable · jquery/jquery
jquery/selector-sizzle.js at 2.1-stable · jquery/jquery

この2つのファイルが差分になってる模様。
Sizzleがない場合、jQueryとしての実装がある感じ。

Sizzleを使ってない場合のfind

jquery/selector-native.js at 2.1-stable · jquery/jquery

94行目あたり。
というわけで、単にqurySelectorAllしたものをラップして返すようになってます。
なので、querySelectorAllが存在してて、ちゃんと動く前提であることがわかります。

このファイルの冒頭のコードコメントにも、外すなら自己責任でやれよって書いてあります。
そして、このコードのI/Oを保てば、独自のSizzleみたいなのも作れるよとも。

Sizzleを使ってる場合のfind

Sizzleのコンストラクタ?に渡ります。

sizzle/sizzle.js at master · jquery/sizzle

215行目あたりにご注目。
セレクタ文字列をパースした結果、

  • #idならgetElementById
  • tagならgetElementsByTagName
  • .classならgetElementsByClassName
  • それ以外はquerySelectorAllが使えれば使う
  • それでもダメなセレクタはパースしてあれこれする関数へ(1919行あたり)

奥へ行けば行くほどパフォーマンスは落ちるので、目安として覚えておきたいところ。

Sizzleを外すメリット

というわけで、です。

  • jQueryセレクタはほぼ#idしか使わないようにしている
  • querySelectorAllがバグなく動く環境が対象である
  • かつ、qSAのパフォーマンスも気にならない環境である

そういう条件下なら、初期化コストとファイル容量がちょっと下がるのでパフォーマンスは良くなる。
まぁならなんでjQuery使ってるのっていう話になるパターンばっかな気はするけど。

まあjQueryだけ読み込んで何もしないページのパフォーマンス測っても、
10msくらいしか早くならなかったので、そうそう選ばない選択肢かなー。(@PC/ChromeCanary)

Sizzleを外すデメリット

  • querySelectorAllが遅い環境の場合、余計に遅くなる可能性がある
  • querySelectorAllのバグfixやpolyfillがない
  • その他Sizzleのブリッジ関数たちも同じく
  • やんちゃなセレクタが使えない

querySelectorAll

モバイルではすっごい遅かったイメージがあって、暗黙の内に使ってなかったんですけど、
やっぱりまだちょっと遅いですね・・。

単一の要素をひたすらにgetElementしてかかった時間を見るだけの雑なテストコードを置いておくので、
気になる方は実機でぜひどうぞ。

DOM Selectors performance comparison.

実機で見たら案の定だった



Backbone.Marionette使ってるなら

Marionette使ってる場合にViewでよく使うuiってありますよね。
あのMarionette.Viewは、uiオブジェクトを作る時に全部findで探しに行く実装になってるので、
せっかくのidも、もれなく全部querySelectorAllされるハメになります。

どういう意味かわかりますよね・・。

ふと

IE9未満を非対応にすることで生まれたjQuery2.x系。
ならなんでIE6+なSizzleを使い続けてるんやろ?って思ったけど、
jQuery(というかSizzle)のオレオレセレクタは、そもそもquerySelectorAllでも使えないからですね。

(あんなセレクタ使うやつはモグリではないかと思ったりもするけど、)jQuery的な発想なら妥当な選択やったんですかねー。

時代はモバイルや!とか言いつつ、出てくるライブラリってPCのことばっか見てたりするし、
モバイル特化のそういうライブラリ群作るなら今ですよ奥さん!きっと需要あるよ!