jQuery版本:2.0.3 DOM載入有關的擴展 isReady:DOM是否載入完(內部使用) readyWait:等待多少文件的計數器(內部使用) holdReady():推遲DOM觸發 ready():準備DOM觸發。 jQuery.ready.promise=function(){}; 監聽 ...
jQuery版本:2.0.3
DOM載入有關的擴展
- isReady:DOM是否載入完(內部使用)
- readyWait:等待多少文件的計數器(內部使用)
- holdReady():推遲DOM觸發
- ready():準備DOM觸發。
- jQuery.ready.promise=function(){}; 監聽DOM的非同步操作(內部使用)
一、$(function(){})和原生window.onload的關係
這個在面試中也是經常會被問到的。從下麵幾個角度來分析一下它們的區別
1、執行時機
頁面載入,先載入節點,再載入文件,比如img文件,flash等。
$(function(){})DOM載入完執行。可能DOM元素關聯的東西並沒有載入完。
window.onload等節點和文件都載入完執行。
對應的事件監聽
jQuery用的是DOMContentLoaded事件。
DOMDContentLoaded:原生DOM載入事件,這個事件觸發代表DOM載入完了。
我之前寫過一篇文章的頁面載入時間分析里也有提到。
onload對應的是load事件。
2、個數
window.onload不能寫多個,後面的會覆蓋前面的。
$(function(){})可以寫多個。都會執行 。
3、簡化寫法
$(function(){})是$(document) .ready(function(){});的簡化寫法。
window.onload沒有簡化寫法。
二、jQurey如何實現DOM ready的
jQuery直接調用DOMContentLoaded來實現DOM的ready。但是DOMContentLoaded和onLoad一樣,瀏覽器只執行一次,jQuery用什麼判斷是否已經執行過呢?document.readyState就是判斷這個的依據。
readyState是document的屬性,總共有3個值:
- loading:文檔正在載入中
- interactive:文檔已經載入完成,正在進行css和圖片等資源的載入
- complete:文檔的所以資源載入完成
判斷完之後如何回調呢?就是用Promise。jQuery通過new一個$.Deferred(promise)對象來實現對DOM的ready的回調,在DOMContentLoaded中將這個promise給resolve掉,這樣就執行了之前註冊的回調函數,同時後面新註冊的回調也會立刻執行。
但是在調用promise之前,jQuery執行了一次setTimeout,因為jQuery.Promise是不會產生非同步的,這和標準的promise規範是不一樣的,所有jQuery自己又手動做了一次setTimeout來實現非同步。這樣使得無論使用在DOM的ready之前註冊的回調還是之後註冊的回調都會在非同步中執行。
三、源碼整體實現邏輯
$(fn)==>new一個 jQuery.fn.init(fn)==>返回$(document).ready( fn)。也就是說
- $(function(){}) =》
- 調用$(document).ready(function(){})=》
- 相當於$().ready(fn)實例方法=》
- 調用的jQuery.ready.promise().done(fn)=》
- jQuery.ready.promise中不管if還是else最終都是調用jQuery.ready(),並返回promise=》
- jQuery.ready()裡面重點是readyList.resolveWith( document, [ jQuery ] );已完成。至此DOM載入完畢就可以調用fn了。
四、實現細節
1、重點是:jQuery.ready.promise()方法【巧妙】
如果DOM已經載入完成了,調用jQuery.ready()這個工具方法;
如果DOM沒載入完,監聽DOMContentLoaded事件和load事件,等事件發生時回調completed(),最終也是調用jQuery.ready()這個工具方法;
var // A central reference to the root jQuery(document) rootjQuery, // The deferred used on DOM ready readyList; jQuery.ready.promise = function( obj ) { if ( !readyList ) { //第一次readyList為空可以進來,後續就進不來if了,只執行一次 readyList = jQuery.Deferred(); //第一步,創建延遲對象 if ( document.readyState === "complete" ) { //DOM載入完成的標誌就是document.readyState為complete,如果DOM已經載入好了就直接調工具方法jQuery.ready。 setTimeout( jQuery.ready );//加定時器是為了相容IE } else {//DOM沒有載入完檢測,即檢測了DOMContentLoaded事件,也檢測了load事件;最終走回調completed函數 // Use the handy event callback document.addEventListener( "DOMContentLoaded", completed, false ); // A fallback to window.onload, that will always work window.addEventListener( "load", completed, false ); //因為火狐瀏覽器會緩存load事件,為了第一時間相應所以對load也監聽了 } } return readyList.promise( obj ); };
completed回調函數如下,最終調用的也是jQuery.ready()。
// The ready event handler and self cleanup method completed = function() { //不管是DOMContentLoaded事件還是load發生,都會取消2個事件監聽 //jQuery.ready()只會觸發一次 document.removeEventListener( "DOMContentLoaded", completed, false ); window.removeEventListener( "load", completed, false ); jQuery.ready(); };
2、jQuery.ready()工具方法做了些什麼
先做個測試:
$(function(arg){ alert(this); //[object HTMLDocument] alert(arg); //jQuery函數 })
再做個測試:
除了$(function(){});$(document).ready(function(){}),也可以把ready當做事件來處理如下。
<script> $(document).on("ready",function(){ alert(123); //123 }); </script>
之所以會自動彈出123,是因為在jQuery源碼中用這句話jQuery( document ).trigger("ready").off("ready");來主動觸發了ready事件,觸發後再取消掉。
// Handle when the DOM is ready ready: function( wait ) { //參數和holdReady有關 // Abort if there are pending holds or we're already ready if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { return; } // 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; } //第一步看這裡重點,resolveWith改變狀態的時候傳參了,給done中方法fn傳入了參數, //document是fn的this指向,jQuery是參數 // 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(true); $(function () { alert(123); //調用了holdReady並傳參true,就不能彈出123了 });
可以推遲,也可以釋放推遲,釋放了以後就可以觸發了。
$.holdReady(true); $.holdReady(false); $(function () { alert(123); //釋放了holdReady,就彈出123 });
這個有什麼用?
比如:
$.getScript('js/a.js',function(){ //非同步載入,不會影響後續代碼執行。可能會產生一個問題,alert(2)先執行了a.js還沒有載入完 }) $(function () { alert(2);//先彈2,後彈出1 });
很多時候引入外部文件的時候,都想等外部文件或者插件載入完,再去觸發操作,操作可能用到a.js。現在這個順序不對,會出問題。
怎樣解決這個問題?用holdReady()方法。
$.holdReady(true); //在這裡先hold住 $.getScript('js/a.js', function () { //非同步載入,不會影響後續代碼執行。可能會產生一個問題,alert(2)先執行了a.js還沒有載入完 $.holdReady(false); //載入完釋放,不hold了就可以彈2了 }) $(function () { alert(2);//先彈2,後彈出1 });
再深入一點,holdReady() 要針對的文件可能不止一個,有很多個,所以要等所有的文件都載入完再執行,所以源碼中有一個計數的readyWait。
源碼中定義了一個等待棧變數——readyWait,每次執行$.holdReady(true)都會增加壓棧,而每次$.holdReady()執行都會彈棧,等空棧的時候就執行jQuery.ready函數,即將promise給resolve掉。
// 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, //第二步看這裡 //holdReady推遲DOM的觸發 // Hold (or release) the ready event holdReady: function( hold ) { if ( hold ) { jQuery.readyWait++;//hold為真,讓readyWait加加處理 } else { jQuery.ready( true ); } }, // Handle when the DOM is ready ready: function( wait ) { //參數和holdReady有關 // Abort if there are pending holds or we're already ready //readyWait為0時就不用hold了,執行後面的操作 if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { return; } // Remember that the DOM is ready jQuery.isReady = true; //預設是false // If a normal DOM Ready event fired, decrement, and wait if need be if ( wait !== true && --jQuery.readyWait > 0 ) { return; } //第一步看這裡重點,resolveWith改變狀態的時候傳參了,給done中方法fn傳入了參數, //document是fn的this指向,jQuery是參數 // 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"); } },
剛開始看源碼,很多地方理解也不到位,解釋可能也不清楚。
參考:
http://www.cnblogs.com/aeexiaoqiang/p/6525702.html
本文作者starof,因知識本身在變化,作者也在不斷學習成長,文章內容也不定時更新,為避免誤導讀者,方便追根溯源,請諸位轉載註明出處:http://www.cnblogs.com/starof/p/6856572.html有問題歡迎與我討論,共同進步。