てっく煮ブログさんのエントリー
jQuery を高速に使う CSS セレクタの書き方 - てっく煮ブログ
をみてて、jQuery の $("#xxxx") を速くしたいと考えていたことを思いだした。それについて書く。

jQuery の $("#xxxx") について

セレクタをどう処理しているのか jquery-1.2.6.js で確認してみよう。
まず下記の init が $() に相当する。Handle HTML stringsのところで引数が string だと quickExpr.exec が走る。これは正規表現でのセレクタ解析処理である。
その後、HANDLE: $("#id")のところでマッチして取得した ID を使って、var elem = document.getElementById 使い、それを return jQuery(elem) として再び init が呼び出される。

jQuery.fn = jQuery.prototype = { init: function( selector, context ) { // Make sure that a selection was provided selector = selector || document; // Handle $(DOMElement) if ( selector.nodeType ) { this[0] = selector; this.length = 1; return this; } // Handle HTML strings if ( typeof selector == “string” ) { // Are we dealing with HTML string or an ID? var match = quickExpr.exec( selector ); // Verify a match, and that no context was specified for #id if ( match && (match[1] || !context) ) { // HANDLE: $(html) -> $(array) if ( match[1] ) selector = jQuery.clean( [ match[1] ], context ); // HANDLE: $("#id") else { var elem = document.getElementById( match[3] ); // Make sure an element was located if ( elem ){ // Handle the case where IE and Opera return items // by name instead of ID if ( elem.id != match[3] ) return jQuery().find( selector ); // Otherwise, we inject the element directly into the jQuery object return jQuery( elem ); } selector = []; } // HANDLE: $(expr, [context]) // (which is just equivalent to: $(content).find(expr) } else return jQuery( context ).find( selector ); // HANDLE: $(function) // Shortcut for document ready } else if ( jQuery.isFunction( selector ) ) return jQuery( document )[ jQuery.fn.ready ? “ready” : “load” ]( selector ); return this.setArray(jQuery.makeArray(selector)); },

よって、コストがかかっているのは正規表現の解析部分のようなのでこの部分を飛ばして、いきなり jQuery に DOM ノードを渡してあげると速くなりそうだ。

高速化方法とまとめ

IDによる jQuery オブジェクトの取得を下記のように定義しておくと簡単に速くできそうだ。($$ にしたのは prototype.js が $(“xxx”) で取れたりそれっぽいから)

function $$(id) { return $(document.getElementById(id)); }

(追記)上記の $$ の定義では、指定した ID が DOM ツリーに存在しないときの処理を考慮していない。

Windows Vista SP1 での計測実験 / 1000回実行の計測三回平均

IE 7.0

Firefox 3.0

Opera 9.6

Safari 3.2

jQuery $("#id")

707 ms

301 ms

157 ms

129 ms

document.getElementById(“id”)

201 ms

43 ms

35 ms

21 ms

jQuery $$(“id”)

470 ms

126 ms

53 ms

51 ms

document.getElementById は参考値としてです。いずれもブラウザでも $$ による取得の方が速くなっていることがわかる。ただ、IE では他のブラウザほど速くならなかったのが残念だ。

セレクタで ID を指定して取得を行うことはよくある。アプリの規模によってはこういった高速化を施しておくのも良いだろう。

テスト用のHTML

上記の計測に使った HTML ソースをべた貼りしておく。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<html>
<head>
<meta  http-equiv="Content-Type"  content="text/html; charset=UTF-8" />
<meta  http-equiv="content-script-type"  content="text/javascript">
<title>jQuery getElementById() の計測テスト</title>
<script  type="text/javascript"  src="jquery-1.2.6.js"></script>
<script  type="text/javascript">
function $$(id)  {
  return $(document.getElementById(id));
}
function printResult(id, ms){
  var p = document.createElement('p');
  p.innerHTML = id + " : " + ms + "ms";
  document.getElementById(id).appendChild(p);
}
function runTest(testfunc, count){
  var s1 = new  Date().getTime();
  for  (var i = 0; i < count; i++)
    testfunc();
  return  new  Date().getTime() - s1;
}
function getCount(){
  return  document.getElementById('textCount').value + 0;
}
function testJquery(count){
  var funcA = function(){
  var d = $("#dummy");
  };
  printResult('jQuery', runTest(funcA, count)  );
}
function testGetElementById(count){
  var funcA = function(){
  var d = document.getElementById("dummy");
  };
  printResult('getElementById', runTest(funcA, count)  );
}
function $$(id)  {
  return $(document.getElementById(id));
}
function test$$(count){
  var funcA = function(){
  var d = $$("dummy");
  };
  printResult('newGetElementById', runTest(funcA, count)  );
}
function newGetElementByIdTest(){
  alert($$("dummy").find(".best").text());
}
</script>
<style  type="text/css">
div { border-top:1px solid #666; margin:10px 0; padding:5px; }
</style>
</head>
<body>
<div><p>テスト回数 : <input  type="text"  id="textCount"  value="1000"/></p></div>
<div><p  id="dummy">テストノード <span  class="best">取得テスト用</span> dummy</p></div>
<div  id="jQuery">
<pre>
var d = $("#dummy");
</pre>
<p><button  onclick="testJquery(getCount())">jQuery 判定</button></p>
</div>
<div  id="getElementById">
<pre>
var d = document.getElementById("dummy");
</pre>
<p><button  onclick="testGetElementById(getCount())">getElementById 判定</button></p>
</div>
<div  id="newGetElementById">
<pre>
function $$(id) {
return $(document.getElementById(id));
}
var d = $$("dummy");
</pre>
<p><button  onclick="test$$(getCount())">$$ 判定</button></p>
</div>
<div  id="newGetElementByIdTest">
<pre>
alert($$("dummy").find(".best").text());
</pre>
<p><button  onclick='alert($$("dummy").find(".best").text())'>$$ が jQuery オブジェクトか確認</button></p>
</div>
</body>
</html>

jQuery 1.3 において(2009/1/18 追記)

上記を jquery-1.3.min.js に差し替えて計測してみましたが、変わらずでした。