jQuery の ID で対象ノードを取得する処理を高速にしたい
てっく煮ブログさんのエントリー
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 |
セレクタで ID を指定して取得を行うことはよくある。アプリの規模によってはこういった高速化を施しておくのも良いだろう。
テスト用のHTML
上記の計測に使った HTML ソースをべた貼りしておく。
<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>