読者です 読者をやめる 読者になる 読者になる

jQueryを読み解く3(contextについて)

前回確認した contextについて、以下の参考記事を元にbenchmarkを取ってみました。
今回はcode readingなしです(全然進みません)。

参考記事

Brandon Aaron「誤解されてるjQueryの"Context"」 - 以下斜め読んだ内容
こちらの記事が詳しく、興味深いです。

benchmarkの取り方

以下の簡易的に計測するメソッドを用いてブラウザのconsole上で計測しています。
(もっといい方法があれば教えて下さい。)
対象ブラウザは、chrome(45.0.2454.85 (64-bit))、Firefox(40.0.3)です。
IEとかAndroidとかで計測すると、いろいろ違ってくるのかもしれませんが面倒なので実施して無いです。
対象サイトは、http://api.jquery.com/ で、 id=post-45 要素の配下にある header タグを取得するというセレクタのパターンを4種類用意して実施してみました。

function benchmark(callback, loopCount) {
  loopCount = (loopCount === undefined) ? 100000 : loopCount;

  var benchmarkKey = 'benchmark ' + loopCount;
  console.time(benchmarkKey);
  for (var i = 0; i < loopCount; i++) {
    callback();
  }
  console.timeEnd(benchmarkKey);
}

本題

1. contextの指定

//誤解してる人の意図に反してcontextはdocument要素のまま
$('a', '#myContainer').context; 
//ちなみに上は内部的には下のように変換
$('#myContainer').find('a');

なるほど、まずは以下の4つでcontextを確認してみます。

No. Pattern Result
1 $('#post-45').find('header').context; document
2 $('header', $('#post-45')[0]).context; #post-45 (意図したcontext)
3 $('#post-45 header').context; document
4 $('header', '#post-45').context; document

NO.2 のみ意図した結果となりました。

2. 高速なのは?

 //DOM指定の高速な順番はこれ
$('#myContainer').find('a'); //1
$('a', $('#myContainer')[0]);//2 find()使うよりわずかに遅い
$('#myContainer a');//3

「1」と同様のパターンで計測してみます。

No. Pattern Result(Chrome) Result(Firefox)
1 benchmark(function() { $('#post-45').find('header'); }) 244.543ms 293.31ms
2 benchmark(function() { $('header', $('#post-45')[0]); }) 303.033ms 402.35ms
3 benchmark(function() { $('#post-45 header'); }) 259.614ms 11659.9ms
4 benchmark(function() { $('header', '#post-45'); }) 259.173ms 385.02ms

以下わかったこと

1) No.4 のように間違ったcontextでもそこそこ速いです。
No.2とNo.4の差は、誤差程度です。
つまり、間違ってcontextを指定しても、そこそこ軽快に動作してくれています。
jQueryが以下のように実行してくれているので、そんなに悲観することはなさそうです。

// 1. (constructor はjQuery)
this.constructor( context ).find( selector );
// 2.
( context || rootjQuery ).find( selector );

2) No.3 は実行環境によっては遅いです。
Chromeだと結構早く動作するのですが、Firefoxだと超絶遅いです。
遅くなる理由も参考記事に詳しく書かれています。

Sizzleはセレクタの「右側」からノードの指定を行っていく
Sizzleの動作はブラウザのCSSセレクタの解釈と同じ

でも、Chromeは非常に高速です。どういうことなんでしょうか。
ChromeだとNo.1〜4はほぼ差がありません。

3. .on() では?

気になるのが、参考記事では .live() について言及されています。
.on()(.live()) はまだソースみていないのですが、気になったのでこちらも計測してみました。

No. Pattern Result(Chrome) Result(Firefox)
1 benchmark(function() { $('#post-45').find('header').on('click', {} ); }) 827.703ms 814.73ms
2 benchmark(function() { $('header', $('#post-45')[0]).on('click', {} ); }) 888.886ms 935.77ms
3 benchmark(function() { $('#post-45 header').on('click', {} ); }) 1047.385ms 13138.24ms
4 benchmark(function() { $('header', '#post-45').on('click', {} ); }) 942.957ms 1219.65ms

どうやら、No.4 の指定方法だと若干遅くなっているようですが、さほど(No.3ほどではない)影響は無いようです。

結論

No.3 の$('#post-45 header') のセレクタを連結する方法は、DOMの状況や環境によっては問題が出てくる可能性あり。
速度を気にするなら、No.1 のfind を使う。
どうやら、No.4 の間違ったcontext指定でもあまり影響はなさそうですが、
(積極的ににこれで良いとは言い切れないので)極力正しい方法で指定したほうが多分良いかと思います。

参考

jQueryを読み解く2(40〜90行目) - rochefort's blog