在進行實驗和資料查詢時,我遇到了幾個關鍵問題:
1. window.onload到底是什么加載完觸發?
2. body為什么會有onload事件?
3. 為什么是window.onload,而不是document.onload?
4. document ready到底是什么ready,DOM渲染完成?
5. jQuery怎么實現$(document).ready?
6. jQuery的ready,還能ready什么?
7. jQuery的document ready就一定比window.onload快嗎?
8. 為什么外部script文件放頁面內容后面好,是一定的嗎?
onload
load是一個事件,會在頁面包含文件資源加載完成后觸發。
支持該事件的HTML標簽:
body、frame、frameset、iframe、img、link、script。
這里有兩點我很在意的是:
1. 其他標簽是沒有該事件,所以不要胡亂用。
2. 就如我開頭提的問題之一,為什么body會有onload?(Question 2)
首先,我為什么有這個疑問,因為可以看出的是,支持該事件的其他標簽都是為了加載標簽里的src資源,而body顯然不是。
我在《JavaScript高級程序設計》找到一個合理的解釋:
一般來說,在window上面發生的任何事件都可以在body元素中通過相應的特性來指定,因為在HTML中無法訪問window元素。這樣是為了保證向后兼容的權宜之計,但所有瀏覽器都能很好地支持這種方式。
結論:為了對應上window.onload,body標簽才有onload。
支持該事件的JavaScript對象:
image、window。
這里有兩個問題我很在意:
1. window.onload到底是什么加載完觸發?(Question 1)
依資料記載,當頁面完全加載后(包括所有圖像、JavaScript文件等外部資源),就會觸發該事件。這方式跟在body上寫onload效果是一樣的。
2. 為什么是window.onload,而不是document.onload? (Question 3)
按上面那個問題的解釋,明明是整個document里面完全加載后觸發,那為什么是window.onload(在window上是什么鬼),而不是document.onload?
還是按《JavaScript高級程序設計》上解釋:
根據“DOM2級事件”規范,應該是在document而非window上面觸發load事件。但是,所有瀏覽器都在window上面實現了該事件,以確保向后兼容。
結論:道理是這么道理,但大家都這么干了。
jQuery document ready
從原生js來講,並沒有ready這種事件。
那么 document ready到底是什么ready了?(Question 4)
按資料說明,這個事件指的是文檔結構(DOM)加載完成觸發的。
PS:這解釋還算合理,就放過這問題。
那jQuery怎么實現$(document).ready?(Question 5)
下面我嘗試解析jquery3.0.0版本(兼容IE9+,現代瀏覽器)里面關於ready事件的實現!
注意:
版本我選擇比較新的3.0.0,相比於較舊版本(例1.x)的,里面的實現會簡單一些,因為舍棄一些兼容代碼。不過實現的原理是一樣,倒不需要多個版本都詳看。
原理:
在jquery腳本加載的時候,會監聽DOMContentLoaded事件(監聽load是補救后路)。當事件觸發時候,會執行ready事件的回調。
代碼:
var readyList = jQuery.Deferred(); //保存所有DOM加載完后執行的函數。 jQuery.fn.ready = function( fn ) { readyList.then( fn ); return this; }; jQuery.extend( { //標記DOM ready事件是否觸發過。 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; } // 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 ] ); } } ); jQuery.ready.then = readyList.then; // The ready event handler and self cleanup method function completed() { document.removeEventListener( "DOMContentLoaded", completed ); window.removeEventListener( "load", completed ); jQuery.ready(); } // Catch cases where $(document).ready() is called // after the browser event has already occurred. // Support: IE <=9 - 10 only // Older IE sometimes signals "interactive" too soon if ( document.readyState === "complete" || ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { // Handle it asynchronously to allow scripts the opportunity to delay ready window.setTimeout( jQuery.ready ); } else { // Use the handy event callback document.addEventListener( "DOMContentLoaded", completed ); //其實最奸詐是這里,在不支持DOMContentLoaded事件的瀏覽器,用load事件代替 // A fallback to window.onload, that will always work window.addEventListener( "load", completed ); }
特別地方:
1. 注冊事件是用addEventListener,所以該jquery版本應該是只支持IE9+。
2. jQuery的ready,還能ready什么?(Question 6)
ready函數僅能用於當前document,因此無需選擇器,所以不能ready其他元素。
三種姿勢使用該函數:
$(document).ready(function) $().ready(function) $(function)
誰更快?
jQuery的document ready就一定比window.onload快嗎?(Question 7)
我寫了一個例子來實驗:
<!DOCTYPE HTML> <html lang="en-US"> <head> <meta charset="UTF-8"/> <title>加載時機</title> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.js" onload="console.log('jquery.js loaded')"></script> <script> console.log('define functions'); function load(type, info){ console.log(type + ' onload ' + (info || ""), new Date().getTime()); } $(document).ready(function () { load('document ready'); }); document.onload = function () { load('document'); }; window.onload = function () { load('window'); }; window.addEventListener("load",function(){ load('window addEventListener'); }); document.addEventListener( "DOMContentLoaded", function () { load('DOMContentLoaded'); }); </script> </head> <body onload="load('body')"> <div onload="load('text')">test</div> <img onload="load('img',1)" src="http://www.deskcar.com/desktop/else/2013714232149/17.jpg" /> <img onload="load('img',2)" src="http://www.deskcar.com/desktop/else/2013714232149/16.jpg" /> <script onload="load('js')" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.2.0/react.min.js"></script> </body> </html>
這種大體有兩種結果:
首次加載:

二次加載:

第一種情況非常符合我們的想法,ready比onload快,順序也比較合理。
而第二種情況就有些怪異,應該依照上面jquery ready事件的實現,那ready應該要DOMContentLoaded后面啊。我思來想去,我覺得這是個誤會,由於二次加載時利用到緩存,導致文件資源都很快加載,各個事件觸發的時間非常相近,順序也不定,就給人一種ready順序不對之感,大家應該發現這幾個事件都是在幾十毫秒之內觸發。
PS:js執行需要時間,幾十毫秒不同的順序我覺得很正常。另個嘗試幾次,二次加載順序確實會有變化,但時間都很相近。
所以,jQuery的document ready不一定比window.onload快執行。
為什么外部script文件放頁面內容后面好?
script執行順序:
《JavaScript高級程序設計》說過——無論如何包含代碼,只要不存在defer和async屬性,瀏覽器都會按照<script>元素在頁面中出現的先后順序對它們依次進行解析。換句話說,在第一個<script>元素包含的代碼解析完成后,第二個<script>包含代碼才會被解析,然后才是第三個.....
放head元素里:
<!DOCTYPE HTML> <html lang="en-US"> <head> <meta charset="UTF-8"/> <title>Example</title> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.js"></script> <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/react/15.2.0/react.min.js"></script> </head> <body> </body> </html>
在head元素里包含所有JavaScript文件,就必須等到全部JavaScript代碼都被下載、解析和執行完成以后,才能呈現頁面的內容(瀏覽器在遇到<body>標簽時才開始呈現內容)。在需要很多JavaScript文件時候,瀏覽器呈現頁面會出現明顯的延遲,延時期間瀏覽器是一片空白。
所以,外部script文件放頁面內容后面。這樣,在解析JavaScript代碼之前,頁面內容將完全呈現出來。
一定是放頁面內容后面嗎?
有種情況是JavaScript放哪里都一樣的,那就是內容是依賴JavaScript的執行渲染時候,放哪都一樣。
總結
雖然這篇文章是簡單的問題,但有時我們就是連簡單的東西都沒搞懂,還以為我們懂了。
本文為原創文章,轉載請保留原出處,方便溯源,如有錯誤地方,謝謝指正。
