若是你們想繼續看下面的內容的話,有一個要求,就是回答我一個問題:
你這樣寫過代碼嗎?css
window.onload = function(){ $(".gravatar").on('click',function(){ //... }); //以及其餘操做DOM的節點 }
若是答案是 yes. 那麼,bingo, 這裏咱們將深刻講解,這樣寫代碼到底有沒有IQ。
若是答案是 No. 那麼,2333333, 你也能夠看一下。 萬一哪天用上了呢?
可能會有童鞋反問,那麼,我改怎麼寫呢?
沒錯,這裏就是說的就是這個。
使用過jquery的童鞋,應該知道有一個叫作ready的方法.
即:前端
$(document).ready(function(){ //操做DOM相關 //... })
那這個和上面的寫法有什麼區別呢? 誰比較好一點呢(指性能)?
wait wait wait ~
這問題有點多誒。 不急。 想一想看, jquery老大哥 就是幫你 提升性能的,確定是下面那種好呢。
Why?
緣由咱們接着說.jquery
頁面加載就是從你輸入網址+enter開始,發生的一些列過程,最終到頁面顯示。 從微觀上分的話,頁面加載有兩部分
一個是以DOMContentLoaded觸發爲標誌的DOM樹的渲染完成
一個是以輔助資源img,font加載完成爲觸發標誌的onload事件
他們兩個的具體區別就是"資源的加載"這個關鍵點.程序員
在得到數據響應後,頁面開始解析,發生的過程爲:
(1) 解析HTML結構。
(2) 加載外部腳本和樣式表文件。
(3) 解析並執行腳本代碼。
(4) 構造HTML DOM模型。//ready執行
(5) 加載圖片等外部文件。
(6) 頁面加載完畢。//load執行segmentfault
其實,說到這裏,這篇文章就已經結束了。
想得美。
這只是,頁面加載很淺的一塊,前端能在頁面加載上作的工做其實超級多。 要知道, 從你輸入網站 + enter鍵後,發生的過程爲:
重定向=>檢查DNS緩存=> DNS解析 => TCP鏈接 => 發請求 => 獲得響應=> 瀏覽器處理 => 最後onload瀏覽器
你能夠數一數,前文的頁面加載和這裏的頁面加載的範圍究竟是怎樣的一個區別. 也就是說上文的頁面加載其實 只算是
瀏覽器處理=> 最後onload這一過程。 懂吧。 很小很小。
因此,這裏咱們先從宏觀上來說解一下,頁面加載的整個流程.緩存
這樣,幹講頁面加載真的很沒趣誒, 又沒有吃的,又沒有程序員鼓勵師,又沒有leader的加薪,憑藉的是本寶寶的 滿腔熱情 和 對技術的執着。 感動吧~
開玩笑的, 意淫了以後。咱們說正事。
若是咱們想深刻了解宏觀頁面加載,須要掌握ECMA5新給出的一個API。 performance . 是否是 感受很熟悉呢?安全
之前,咱們來檢查瀏覽器的時候,大部分狀況下是使用app
console.time(specialNum); console.timeEnd(specialNum);
或者dom
new Date().getTime(); //或者 Date.now();
上面說的兩種方法, 獲取的精度都是毫秒級(10^-6),對於一些很是精確的測試,他們的做用來仍是蠻有限的,並且獲取數據的方式,也比較complicated.
ES5提出的performance能夠獲取到,微秒級別(10^-9). 並且,可以獲得後臺事件的更多時間數據。
他的兼容性是IE9+ 。 以爲已經足夠了。
一般,咱們能夠從performance.timing對象上,得到咱們想要的一切時間值.具體有哪些,我就不贅述了。直接看一張圖:
(from Sam Dutton)
好比,咱們得到重定向時間用:
var time = performance.timing; var redirect = time.redirectEnd - time.redirectStart; //單位爲微秒
這就已經夠咱們用的啦。
裏面須要進行一點解釋
即DOMContentLoaded事件 是在domContentLoaded那段觸發的。圖中所指的domContentLoaded其實分爲兩塊, 一個是domContentLoadedEventStart和domContentLoadedEventEnd. 詳見下述說明:(from 賴小賴小賴)
// 獲取 performance 數據 var performance = { // memory 是非標準屬性,只在 Chrome 有 // 財富問題:我有多少內存 memory: { usedJSHeapSize: 16100000, // JS 對象(包括V8引擎內部對象)佔用的內存,必定小於 totalJSHeapSize totalJSHeapSize: 35100000, // 可以使用的內存 jsHeapSizeLimit: 793000000 // 內存大小限制 }, // 哲學問題:我從哪裏來? navigation: { redirectCount: 0, // 若是有重定向的話,頁面經過幾回重定向跳轉而來 type: 0 // 0 即 TYPE_NAVIGATENEXT 正常進入的頁面(非刷新、非重定向等) // 1 即 TYPE_RELOAD 經過 window.location.reload() 刷新的頁面 // 2 即 TYPE_BACK_FORWARD 經過瀏覽器的前進後退按鈕進入的頁面(歷史記錄) // 255 即 TYPE_UNDEFINED 非以上方式進入的頁面 }, timing: { // 在同一個瀏覽器上下文中,前一個網頁(與當前頁面不必定同域)unload 的時間戳,若是無前一個網頁 unload ,則與 fetchStart 值相等 navigationStart: 1441112691935, // 前一個網頁(與當前頁面同域)unload 的時間戳,若是無前一個網頁 unload 或者前一個網頁與當前頁面不一樣域,則值爲 0 unloadEventStart: 0, // 和 unloadEventStart 相對應,返回前一個網頁 unload 事件綁定的回調函數執行完畢的時間戳 unloadEventEnd: 0, // 第一個 HTTP 重定向發生時的時間。有跳轉且是同域名內的重定向纔算,不然值爲 0 redirectStart: 0, // 最後一個 HTTP 重定向完成時的時間。有跳轉且是同域名內部的重定向纔算,不然值爲 0 redirectEnd: 0, // 瀏覽器準備好使用 HTTP 請求抓取文檔的時間,這發生在檢查本地緩存以前 fetchStart: 1441112692155, // DNS 域名查詢開始的時間,若是使用了本地緩存(即無 DNS 查詢)或持久鏈接,則與 fetchStart 值相等 domainLookupStart: 1441112692155, // DNS 域名查詢完成的時間,若是使用了本地緩存(即無 DNS 查詢)或持久鏈接,則與 fetchStart 值相等 domainLookupEnd: 1441112692155, // HTTP(TCP) 開始創建鏈接的時間,若是是持久鏈接,則與 fetchStart 值相等 // 注意若是在傳輸層發生了錯誤且從新創建鏈接,則這裏顯示的是新創建的鏈接開始的時間 connectStart: 1441112692155, // HTTP(TCP) 完成創建鏈接的時間(完成握手),若是是持久鏈接,則與 fetchStart 值相等 // 注意若是在傳輸層發生了錯誤且從新創建鏈接,則這裏顯示的是新創建的鏈接完成的時間 // 注意這裏握手結束,包括安全鏈接創建完成、SOCKS 受權經過 connectEnd: 1441112692155, // HTTPS 鏈接開始的時間,若是不是安全鏈接,則值爲 0 secureConnectionStart: 0, // HTTP 請求讀取真實文檔開始的時間(完成創建鏈接),包括從本地讀取緩存 // 鏈接錯誤重連時,這裏顯示的也是新創建鏈接的時間 requestStart: 1441112692158, // HTTP 開始接收響應的時間(獲取到第一個字節),包括從本地讀取緩存 responseStart: 1441112692686, // HTTP 響應所有接收完成的時間(獲取到最後一個字節),包括從本地讀取緩存 responseEnd: 1441112692687, // 開始解析渲染 DOM 樹的時間,此時 Document.readyState 變爲 loading,並將拋出 readystatechange 相關事件 domLoading: 1441112692690, // 完成解析 DOM 樹的時間,Document.readyState 變爲 interactive,並將拋出 readystatechange 相關事件 // 注意只是 DOM 樹解析完成,這時候並無開始加載網頁內的資源 domInteractive: 1441112693093, // DOM 解析完成後,網頁內資源加載開始的時間 // 在 DOMContentLoaded 事件拋出前發生 domContentLoadedEventStart: 1441112693093, // DOM 解析完成後,網頁內資源加載完成的時間(如 JS 腳本加載執行完畢) domContentLoadedEventEnd: 1441112693101, // DOM 樹解析完成,且資源也準備就緒的時間,Document.readyState 變爲 complete,並將拋出 readystatechange 相關事件 domComplete: 1441112693214, // load 事件發送給文檔,也即 load 回調函數開始執行的時間 // 注意若是沒有綁定 load 事件,值爲 0 loadEventStart: 1441112693214, // load 事件的回調函數執行完畢的時間 loadEventEnd: 1441112693215 } };
不過performance還有另一個方法 now
一般,咱們會將該方法和Date.now()進行一個對比。
performance.now(); //輸出是微秒級別 Date.now(); //輸出是毫秒級別
其中Date.now()是輸出 從1970年開始的毫秒數.
performance.now()參考的是從.performance.timing.navigationStart(頁面開始加載)的時間, 到如今的微秒數.
這裏,咱們可使用performance.now()來模擬獲取DomContentLoaded的時間。
var timesnipe = performance.now(); document.addEventListener('DOMContentLoaded', function() { console.log(performance.now() - timesnipe); }, false); window.addEventListener('load', function() { console.log(performance.now() - timesnipe); }, false); //可是這樣並不等同於,只能算做約等於 performance.timing.domContentLoadedEventStart - performance.timing.domLoading; //檢測domLoadEvent觸發時間
上面不相等的緣由就在於,當執行script的時候,DOM其實已經開始解析DOM和頁面內容, 因此會形成時間上 比 真實時間略短。另外performance還有其餘幾個API,好比makr,getEntries. 不過,這裏由於和頁面顯示的關係不是很大,這裏就不作過多的講解了。 有興趣,能夠參考:賴小賴小賴
接下來,咱們一步一步來看一下,頁面加載的整個過程.
這是頁面加載的第一步(也有可能沒有). 好比,當一個頁面已經遷移,可是你輸入原來的網站地址的時候就會發生。
或者, 好比example.com -> m.example.com/home 。 這樣耗費的時間成本是雙倍的。 這裏就會通過兩次DNS解析,TCP鏈接,以及請求的發送。因此,在後臺設置好正確的網址是很重要的。
如圖:
這裏,咱們可使用.performance的屬性,計算出重定向時間
redirectTime = redirectEnd - redirectStart
接着咱們就到了cache,DNS,TCP,Request,以及Response的階段
若是咱們的域名輸入正確的話,接着,瀏覽器會查詢本地是否有域名緩存(appCache),若是有,則不須要進行DNS解析,不然須要對域名進行解析,找到真實的IP地址,而後創建3次握手鍊接, 發送請求, 最後接受數據。 一般,這一部分,能夠作的優化有:
發送請求的優化:加異地機房,加CDN.(加快解析request)
請求加載數據的優化:頁面內容通過 gzip 壓縮,靜態資源 css/js 等壓縮(request到response的優化)
ok~ 使用performance測試時間爲:
// DNS 緩存時間 times.appcache = t.domainLookupStart - t.fetchStart; // TCP 創建鏈接完成握手的時間 times.connect = t.connectEnd - t.connectStart; //DNS 查詢時間 times.lookupDomain = t.domainLookupEnd - t.domainLookupStart; //整個解析時間 var lookup = t.responseEnd - t.fetchStart;
其實,只要對照那個圖查查over,不用太關注上面的式子。使用時須要注意,performance的相關操做,最好放在onload的回調中執行,避免出現異常的bug.
這裏的過程其實就和開頭的時候說的同樣
(1) 解析HTML結構。
(2) 加載外部腳本和樣式表文件。
(3) 解析並執行腳本代碼。
(4) 構造HTML DOM模型。//ready執行
(5) 加載圖片等外部文件。
(6) 頁面加載完畢。//load執行
ok~ 這裏,咱們來計算一下時間:
上performance
//計算DOMContentLoaded觸發時間 var contentLoadedTime = t.domContentLoadedEventStart-t.domLoading //計算load觸發時間 var loadTime = t.domComplete - t.domLoading;
更直觀的,咱們能夠在Chrome的developer工具的network選項裏面獲得咱們想要的答案.
這兩個線,分別表明的是DOMContentLoaded和onload觸發的時間。 這也更能直觀的看出,DOMContentLoaded事件比onload事件先觸發吧。如今回到咱們開頭的那個問題。咱們到底該將代碼寫在什麼地方呢?
這裏,這個問題就很好回答了。若是你的js文件涉及DOM操做,能夠直接在DOMContentLoaded裏面添加回調函數,或者說基本上咱們的js文件均可以寫在裏面進行調用. 其實,這和咱們將js文件放在body底部,在js上面加async,defer,以及hard Callback異步加載js文件的效果是同樣同樣的。
上面一部分我有篇文章已經介紹過了,因此這裏就不贅述了。
接下來咱們要作的最後一件事,就是看看jquery老大哥,他的ready事件的原理究竟是什麼.
jquery主要作的工做就是兼容IE6,7,8實現DOMContentLoaded的效果.因爲如今主流只要兼容到IE8, 剩下IE6,7咱們不作過多的分析了。
目前流行的作法有兩種, 一種是使用readystatechange實現,另一種使用IE自帶的doScroll方法實現.
這實際上是IE6,7,8的特有屬性,用它來標識某個元素的加載狀態。 可是如今w3c規定,只有xhr纔有這個事件。 因此,這裏,咱們通常只能在IE中使用readyStateChange不然,其餘瀏覽器是沒有效果的。
詳見:readyState兼容性分析
這樣,咱們模擬jquery的ready事件時就可使用:
document.onreadystatechange = function () { if (document.readyState == "interactive" || document.readyState == "complete") { //添加回調... } }
理想很豐滿,現實很骨感。 事實上, 當readyState爲interactive時, Dom的結構並未徹底穩定,若是還有其餘腳本影響DOM時, 這時候可能會形成bug。 另外爲complete時, 這時候圖片等相關資源已經加載完成。 這個時候模擬觸發DOMContentLoaded事件,其實和onload事件觸發時間並無過久的時間距離。 這種方式兼容低版本IE仍是不太可靠的。
另外提供一個doScroll方式
這是IE低版本特有的,不過IE11已經棄用了。 使用scrollLeft和scrollTop代替. doScroll 的主要做用是檢測DOM結構是否問題, 一般咱們會使用輪詢來檢測doScroll是否可用,當可用的時候必定是DOM結構穩定,圖片資源還未加載的時候。
咱們來看一下jquery中實現doScroll的兼容:
//低版本的IE瀏覽器,這裏添加監聽做爲向下兼容,若是doScroll執行出現bug,也能保證ready函數的執行 document.attachEvent( "onreadystatechange", DOMContentLoaded ); window.attachEvent( "onload", jQuery.ready ); //在ready裏面會對執行作判斷,確保只執行一次 var top = false; // 若是是IE且不是iframe就經過不停的檢查doScroll來判斷dom結構是否ready try { top = window.frameElement == null && document.documentElement; } catch(e) {} if ( top && top.doScroll ) { (function doScrollCheck() { if ( !jQuery.isReady ) {//ready方法沒有執行過 try { // 檢查是否能夠向左scroll滑動,當dom結構尚未解析完成時會拋出異常 top.doScroll("left"); } catch(e) { //遞歸調用,直到當dom結構解析完成 return setTimeout( doScrollCheck, 50 ); } //沒有發現異常,表示dom結構解析完成,刪除以前綁定的onreadystatechange事件 //執行jQuery的ready方法 jQuery.ready(); } })(); } //看看jQuery.ready()方法: ready:function(wait) { if (wait === true ? --jQuery.readyWait : jQuery.isReady) { //判斷頁面是否已完成加載而且是否已經執行ready方法 //經過isReady狀態進行判斷, 保證只執行一次 return; } if (!document.body) { return setTimeout(jQuery.ready); } jQuery.isReady = true; //指示ready方法已被執行 //這也是上面兩次綁定事件的緣由,會保證只執行一次 if (wait !== true && --jQuery.readyWait > 0) { return; } //如下是處理ready的狀態 readyList.resolveWith(document, [jQuery]); if (jQuery.fn.trigger) { //解除引用 jQuery(document).trigger("ready").off("ready"); } }
以上就是jquery 兼容ready的方法。ending~