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'); }); });