jquery的ready方法(准备DOM触发)还是比较复杂的,我们先看流程图:
首先调用jq的raady方法,我们通过源码可以看到,方法内部调用了jQuery.ready.promise()方法。
jQuery.fn = jQuery.prototype = { ........................ ready: function( fn ) { // Add the callback jQuery.ready.promise().done( fn ); return this; }, ..................... };
我们再来看promise干了些什么:
jQuery.ready.promise = function( obj ) { if ( !readyList ) { readyList = jQuery.Deferred(); // Catch cases where $(document).ready() is called after the browser event has already occurred. // we once tried to use readyState "interactive" here, but it caused issues like the one // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15 if ( document.readyState === "complete" ) { // Handle it asynchronously to allow scripts the opportunity to delay ready setTimeout( jQuery.ready, 1 ); // Standards-based browsers support DOMContentLoaded } else if ( document.addEventListener ) { // Use the handy event callback document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); // A fallback to window.onload, that will always work window.addEventListener( "load", jQuery.ready, false ); // If IE event model is used } else { // Ensure firing before onload, maybe late but safe also for iframes document.attachEvent( "onreadystatechange", DOMContentLoaded ); // A fallback to window.onload, that will always work window.attachEvent( "onload", jQuery.ready ); // If IE and not a frame // continually check to see if the document is ready var top = false; try { top = window.frameElement == null && document.documentElement; } catch(e) {} if ( top && top.doScroll ) { (function doScrollCheck() { if ( !jQuery.isReady ) { try { // Use the trick by Diego Perini // http://javascript.nwbox.com/IEContentLoaded/ top.doScroll("left"); } catch(e) { return setTimeout( doScrollCheck, 50 ); } // and execute any waiting functions jQuery.ready(); } })(); } } } return readyList.promise( obj ); };
先判断readyList有没有,没有的话给readyList赋值延迟对象,最后返回readyList.promise,很明显promise函数只会执行一次。
给readyList赋值完后判断document.readyState是否为complete,如果dom节点已加载完成,则调用jQuery.ready方法,否则注册事件DOMContentLoaded与load,用来监听dom的加载进度。
当浏览器有缓存时会先触发load,为了保证第一之间最快的加载dom所以把2个事件都注册上了。
我们来看下事件回调DOMContentLoaded做了些什么
var ................... // The deferred used on DOM ready readyList, // The ready event handler and self cleanup method DOMContentLoaded = function() { if ( document.addEventListener ) { document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); jQuery.ready(); } else if ( document.readyState === "complete" ) { // we're here because readyState === "complete" in oldIE // which is good enough for us to call the dom ready! document.detachEvent( "onreadystatechange", DOMContentLoaded ); jQuery.ready(); } }, ..................
DOMContentLoaded回调很简单,先移除事件,在调用jQuery.ready()方法。所以说最终都是调用它。我们来看一下源码:
jQuery.extend({ ....................... // Is the DOM ready to be used? Set to true once it occurs. isReady: false, // A counter to track how many items to wait for before // the ready event fires. See #6781 readyWait: 1, // Hold (or release) the ready event holdReady: function( hold ) { if ( hold ) { jQuery.readyWait++; } else { jQuery.ready( true ); } }, // Handle when the DOM is ready ready: function( wait ) { // Abort if there are pending holds or we're already ready if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { return; } // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). if ( !document.body ) { return setTimeout( jQuery.ready, 1 ); } // Remember that the DOM is ready jQuery.isReady = true; // If a normal DOM Ready event fired, decrement, and wait if need be if ( wait !== true && --jQuery.readyWait > 0 ) { return; } // If there are functions bound, to execute readyList.resolveWith( document, [ jQuery ] ); // Trigger any bound ready events if ( jQuery.fn.trigger ) { jQuery( document ).trigger("ready").off("ready"); } }, ................... });
ready方法先做了一些判断,这些都是与holdReady有关,那么holdReady是干嘛的,isReady和readyWait又是干嘛的,请看下面的例子:
$.getScript('a.js',function(){ }); $(function(){ alert(2); });
a.js
alert(1);
因为getScript是异步的,所以代码执行顺序为先弹出alert(2) 再弹出alert(1)
那么要怎么样同步呢,这时就需要用到holdReady了
$.holdReady(true); //先hold住,推迟执行 $.getScript('a.js',function(){ $.holdReady(false); //当a.js加载完释放hold }); $(function(){ alert(2); });
现在代码的执行顺序为先alert(1)再alert(2),由此可见,holdReady方法就是起一个推迟释放作用。回头再看ready方法就好理解了。我们再看最后2句代码:
// If there are functions bound, to execute readyList.resolveWith( document, [ jQuery ] ); // Trigger any bound ready events if ( jQuery.fn.trigger ) { jQuery( document ).trigger("ready").off("ready"); }
第一句代码执行了$(docment).ready方法中的回调,并且传递了JQ对象。第二句代码先触发ready事件再清除它。我们看下ready方法的使用:
$(function(scope){ scope(this).on('click',function(){ alert('a'); }); });
scpoe是JQ对象,this是document,最后我们看下jq准备DOM触发的3种写法:
$(function(scope){ scope(this).on('click',function(){ alert('a'); }); }); $(document).ready(function(scope){ scope(this).on('click',function(){ alert('a'); }); }); $(document).on('ready',function(){ $(this).on('click',function(){ alert('a'); }); });