最近在寫一個Javascript的框架,剛把DOMContentLoaded事件封裝好,略帶小興奮,把開發過程中遇到的原理和兼容性問題做篇筆記,省的忘記到處找。
我們在寫js代碼的時候,一般都會添加window.onload事件,主要是爲了在DOM加載完後可以使用getElementById,getElementsByTagName等方法選取DOM元素進行操作,但是window.load會等到加載完DOM、腳本、CSS,最後加載完圖片甚至是iframe中的所有資源纔會觸發,很多時候網頁的圖片較多較大,要等最後圖片這個耗時大戶加載完才執行js明顯有些太遲了,很多時候都會影響用戶體驗。
很多js框架都有個document.ready的功能,像JQuery的$(document).ready()方法,可以在DOM加載完就立即執行js代碼,讓圖片自個慢慢加載吧。
document.ready的核心是DOMContentLoaded事件,firefox、chrome、opera、safari、ie9+都可以使用addEventListener(‘DOMContentLoaded’,fn,false)進行事件綁定,而ie6~8不支持DOMContentLoaded事件,所以要針對ie6~8做兼容性處理。
資料上說ie6~8可以使用document.onreadystatechange事件監聽document.readyState狀態是否等於complete來判斷DOM是否加載完畢,如果頁面中嵌有iframe的話,ie6~8的document.readyState會等到iframe中的所有資源加載完纔會變成complete,此時iframe變成了耗時大戶。但是經過測試,即使頁面中沒有iframe,當readyState等於complete時,實際觸發的是onload事件而不是DOMContentLoaded事件,對這點表示驚奇。
還好ie有個特有的doScroll方法,當頁面DOM未加載完成時,調用doScroll方法時,就會報錯,反過來,只要一直間隔調用doScroll直到不報錯,那就表示頁面DOM加載完畢了,不管圖片和iframe中的內容是否加載完畢,此法都有效。
如果有多個js文件綁定了document.ready事件,爲了防止瀏覽器重複綁定,同時有序執行,可以引入一個事件隊列機制來解決。
以上就是document.ready事件的原理和兼容性問題,下面貼段實例代碼,爲了方便理解執行過程,使用函數封裝的模式,執行過程都寫在註釋裏了,如果有不妥之處歡迎指教。
//保存domReady的事件隊列 eventQueue = []; //判斷DOM是否加載完畢 isReady = false; //判斷DOMReady是否綁定 isBind = false; /*執行domReady() * *@param {function} *@execute 將事件處理程序壓入事件隊列,並綁定DOMContentLoaded * 如果DOM加載已經完成,則立即執行 *@caller */ function domReady(fn){ if (isReady) { fn.call(window); } else{ eventQueue.push(fn); }; bindReady(); }; /*domReady事件綁定 * *@param null *@execute 現代瀏覽器通過addEvListener綁定DOMContentLoaded,包括ie9+ ie6-8通過判斷doScroll判斷DOM是否加載完畢 *@caller domReady() */ function bindReady(){ if (isReady) return; if (isBind) return; isBind = true; if (window.addEventListener) { document.addEventListener('DOMContentLoaded',execFn,false); } else if (window.attachEvent) { doScroll(); }; }; /*doScroll判斷ie6-8的DOM是否加載完成 * *@param null *@execute doScroll判斷DOM是否加載完成 *@caller bindReady() */ function doScroll(){ try{ document.documentElement.doScroll('left'); } catch(error){ return setTimeout(doScroll,20); }; execFn(); }; /*執行事件隊列 * *@param null *@execute 循環執行隊列中的事件處理程序 *@caller bindReady() */ function execFn(){ if (!isReady) { isReady = true; for (var i = 0; i < eventQueue.length; i++) { eventQueue[i].call(window); }; eventQueue = []; }; }; //js文件1 domReady(function(){ ... }); //js文件2 domReady(function(){ ... }); //注意,如果是異步加載的js就不要綁定domReady方法,不然函數不會執行, //因爲異步加載的js下載之前,DOMContentLoaded已經觸發,addEventListener執行時已經監聽不到了
測試頁面:都加載了兩張很大的圖片,onload需要圖片加載完才能執行js,DOMContentLoaded只需等到DOM加載完即可執行js。可以打開firebug查看加載過程,每次測試前記得先清理下瀏覽器緩存。