jQueryの$(document).readyをjQuery非依存にする。
2008/09/09追記
GitHubに置いてみた。これくらいなら、Gistで良かったかも。
bindready/bindReady.js at master · monjudoh/bindready · GitHub
jQueryを使っていないWebサイトで必要最小限のJavaScriptを使って、
DOM構築後のタイミングで実行したい処理を実行したい。
コードリーディング(概略)
とりあえずjQuery1.2.6の該当箇所のコードを読む…のは、
$(document).ready();について - 文殊堂でやった。
省く機能
DOM構築後に呼び出された時即実行→何もしない
jQuery.readyListにpushしてDOM構築後に複数の関数を順繰り実行→一つだけ実行
いろいろくるんでthisとか第一引数とかにいろいろ指定→何もせずに関数をそのまま実行
要はbindReady(someFunction);とやると、
DOM構築後のタイミングでsomeFunction();となるだけの関数を作るとする。
コードリーディング(依存列挙)
2329-2387行目
function bindReady(){ if ( readyBound ) return; readyBound = true; // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event if ( document.addEventListener && !jQuery.browser.opera) // Use the handy event callback document.addEventListener( "DOMContentLoaded", jQuery.ready, false ); // If IE is used and is not in a frame // Continually check to see if the document is ready if ( jQuery.browser.msie && window == top ) (function(){ if (jQuery.isReady) return; try { // If IE is used, use the trick by Diego Perini // http://javascript.nwbox.com/IEContentLoaded/ document.documentElement.doScroll("left"); } catch( error ) { setTimeout( arguments.callee, 0 ); return; } // and execute any waiting functions jQuery.ready(); })(); if ( jQuery.browser.opera ) document.addEventListener( "DOMContentLoaded", function () { if (jQuery.isReady) return; for (var i = 0; i < document.styleSheets.length; i++) if (document.styleSheets[i].disabled) { setTimeout( arguments.callee, 0 ); return; } // and execute any waiting functions jQuery.ready(); }, false); if ( jQuery.browser.safari ) { var numStyles; (function(){ if (jQuery.isReady) return; if ( document.readyState != "loaded" && document.readyState != "complete" ) { setTimeout( arguments.callee, 0 ); return; } if ( numStyles === undefined ) numStyles = jQuery("style, link[rel=stylesheet]").length; if ( document.styleSheets.length != numStyles ) { setTimeout( arguments.callee, 0 ); return; } // and execute any waiting functions jQuery.ready(); })(); } // A fallback to window.onload, that will always work jQuery.event.add( window, "load", jQuery.ready ); }
この中からjQueryのほかの箇所に依存している箇所を列挙
依存除去
readyBound
function bindReady(){ if ( readyBound ) return; readyBound = true; /* ブラウザ別イベント登録処理 */ }
これは初回呼び出し時のみ実処理を実行するということなので、
以下のコードで代替させる。
function bindReady(){ bindReady = function(){}; /* ブラウザ別イベント登録処理 */ }
jQuery.browser.opera〜jQuery.browser.msie
これらのプロパティの元々の定義箇所は1221-1230行目
var userAgent = navigator.userAgent.toLowerCase(); // Figure out what browser is being used jQuery.browser = { version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1], safari: /webkit/.test( userAgent ), opera: /opera/.test( userAgent ), msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ), mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent ) };
bindReady関数は一度しか実行されないのでこれらも関数内で定義してしまって問題ない。
jQuery.browser→browserとして、使用していないプロパティの定義をやめる。
function bindReady(){ bindReady = function(){}; var userAgent = navigator.userAgent.toLowerCase(); var browser = { safari: /webkit/.test( userAgent ), opera: /opera/.test( userAgent ), msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ) }; /* ブラウザ別イベント登録処理 */ }
jQuery.isReady・jQuery.ready
だいぶ機能を削ったので全部再現する必要はない
それぞれjQueryのプロパティからローカル変数に変える
function bindReady(callback){ var isReady = false; function ready(){ if(isReady)return; isReady = true; callback(); } bindReady = function(){}; var userAgent = navigator.userAgent.toLowerCase(); var browser = { safari: /webkit/.test( userAgent ), opera: /opera/.test( userAgent ), msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ) }; /* ブラウザ別イベント登録処理 */ }
jQuery.event.add
jQuery.event.add( window, "load", jQuery.ready );
使用箇所これだけ実質window.onloadに設定できればOKなはず。
function bindReady(callback){ var isReady = false; function ready(){ if(isReady)return; isReady = true; callback(); } bindReady = function(){}; var userAgent = navigator.userAgent.toLowerCase(); var browser = { safari: /webkit/.test( userAgent ), opera: /opera/.test( userAgent ), msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ) }; /* ブラウザ別イベント登録処理 */ var oldOnload = window.onload; window.onload=function(){ if(oldOnload)oldOnload(); ready(); }; }
jQuery("style, link[rel=stylesheet]")
必要なのは↓
jQuery("style, link[rel=stylesheet]").length
なので代替コードを関数にしてやればよい
function countNumStyles(){ var d = document; var stylesLength = d.getElementsByTagName('style').length; var links = d.getElementsByTagName('link'); for(var i = 0; i++; i < links.length){ if(links[i].rel == 'stylesheet'){ stylesLength++; } } return stylesLength; }
コード
こんな感じになった。
function bindReady(callback){ var isReady = false; function ready(){ if(isReady)return; isReady = true; callback(); } bindReady = function(){}; var userAgent = navigator.userAgent.toLowerCase(); var browser = { safari: /webkit/.test( userAgent ), opera: /opera/.test( userAgent ), msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ) }; // Mozilla, Opera (see further below for it) and webkit nightlies currently support this event if ( document.addEventListener && !browser.opera) // Use the handy event callback document.addEventListener( "DOMContentLoaded", ready, false ); // If IE is used and is not in a frame // Continually check to see if the document is ready if ( browser.msie && window == top ) (function(){ if (isReady) return; try { // If IE is used, use the trick by Diego Perini // http://javascript.nwbox.com/IEContentLoaded/ document.documentElement.doScroll("left"); } catch( error ) { setTimeout( arguments.callee, 0 ); return; } // and execute a waiting function ready(); })(); if ( browser.opera ) document.addEventListener( "DOMContentLoaded", function () { if (isReady) return; for (var i = 0; i < document.styleSheets.length; i++) if (document.styleSheets[i].disabled) { setTimeout( arguments.callee, 0 ); return; } // and execute a waiting function ready(); }, false); if ( browser.safari ) { var numStyles; function countNumStyles(){ var d = document; var stylesLength = d.getElementsByTagName('style').length; var links = d.getElementsByTagName('link'); for(var i = 0; i++; i < links.length){ if(links[i].rel == 'stylesheet'){ stylesLength++; } } return stylesLength; } (function(){ if (isReady) return; if ( document.readyState != "loaded" && document.readyState != "complete" ) { setTimeout( arguments.callee, 0 ); return; } if ( numStyles === undefined ) numStyles = countNumStyles(); if ( document.styleSheets.length != numStyles ) { setTimeout( arguments.callee, 0 ); return; } // and execute a waiting function ready(); })(); } var oldOnload = window.onload; window.onload=function(){ if(oldOnload)oldOnload(); ready(); }; }
とりあえずIE6.0、Firefox3.0.1、Opera9.52、Safari3.1、Google Chrome 0.2.149.27 Beta(すべてWindows版)で確認
てきとーにファイルサイズのでかいHTMLをダウンしてきて、
HEAD内に
<script type="text/javascript"> (function(){ function bindReady(callback){ /*中略*/ bindReady(function(){ alert(document.getElementById('hogehoge').innerHTML); }); })(); </script>
BODYの最後に
<span id="hogehoge">fugafuga</span>
とかやってみたら上手くいった。
ここのところのjQueryの元のコードだとテストとかどうしてるんだろ?