詳述:一個鏈接是怎樣打開你的頁面的">詳述:一個鏈接是怎樣打開你的頁面的

分類:IT技術 時間:2017-10-01

如果大家想繼續看下面的內容的話,有一個要求,就是回答我一個問題:

你這樣寫過代碼嗎?

window.onload = function(){
	$(".gravatar").on('click',function(){
		//...
	});
	//以及其他操作DOM的節點
}

如果答案是 yes. 那麽,bingo, 這裏我們將深入講解,這樣寫代碼到底有沒有IQ。 如果答案是 No. 那麽,2333333, 你也可以看一下。 萬一哪天用上了呢? 可能會有童鞋反問,那麽,我改怎麽寫呢? 沒錯,這裏就是說的就是這個。 使用過jquery的童鞋,應該知道有一個叫做ready的方法. 即:

$(document).ready(function(){
    //操作DOM相關
    //...
})

那這個和上面的寫法有什麽區別呢? 誰比較好一點呢(指性能)? wait wait wait ~ 這問題有點多誒。 不急。 想想看, jquery老大哥 就是幫你 提高性能的,肯定是下面那種好呢。

Why?

原因我們接著說.

頁面加載

頁面加載就是從你輸入網址+enter開始,發生的一些列過程,最終到頁面顯示。 從微觀上分的話,頁面加載有兩部分 一個是以DOMContentLoaded觸發為標誌的DOM樹的渲染完成 一個是以輔助資源img,font加載完成為觸發標誌的onload事件 他們兩個的具體區別就是"資源的加載"這個關鍵點.

在獲得數據響應後,頁面開始解析,發生的過程為: (1) 解析HTML結構。 (2) 加載外部腳本和樣式表文件。 (3) 解析並執行腳本代碼。 (4) 構造HTML DOM模型。 //ready執行 (5) 加載圖片等外部文件。 (6) 頁面加載完畢。 //load執行

其實,說到這裏,這篇文章就已經結束了。 想得美。 這只是,頁面加載很淺的一塊,前端能在頁面加載上做的工作其實超級多。 要知道, 從你輸入網站 + enter鍵後,發生的過程為: 重定向=>檢查DNS緩存=> DNS解析 => TCP連接 => 發請求 => 得到響應=> 瀏覽器處理 => 最後onload

你可以數一數,前文的頁面加載和這裏的頁面加載的範圍到底是怎樣的一個區別. 也就是說上文的頁面加載其實 只算是 瀏覽器處理=> 最後onload這一過程。 懂吧。 很小很小。 所以,這裏我們先從宏觀上來講解一下,頁面加載的整個流程. ##宏觀頁面加載 這樣,幹講頁面加載真的很沒趣誒, 又沒有吃的,又沒有程序員鼓勵師,又沒有leader的加薪,憑借的是本寶寶的 滿腔熱情 和 對技術的執著。 感動吧~ 開玩笑的, 意淫了之後。我們說正事。 如果我們想深入了解宏觀頁面加載,需要掌握ECMA5新給出的一個API。 performance . 是不是 感覺很熟悉呢?

performance簡單講解

以前,我們來檢查瀏覽器的時候,大部分情況下是使用

console.time(specialNum);
console.timeEnd(specialNum);

或者

new Date().getTime();
//或者
Date.now();

上面說的兩種方法, 獲取的精度都是毫秒級(10^-6),對於一些非常精確的測試,他們的作用來還是蠻有限的,而且獲取數據的方式,也比較complicated. ES5提出的performance可以獲取到,微秒級別(10^-9). 而且,能夠得到後臺事件的更多時間數據。 他的兼容性是 IE9+ 。 覺得已經足夠了。

performance.timing對象

通常,我們可以從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

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. 不過,這裏因為和頁面顯示的關系不是很大,這裏就不做過多的講解了。 有興趣,可以參考: 賴小賴小賴 接下來,我們一步一步來看一下,頁面加載的整個過程.

redirect

這是頁面加載的第一步(也有可能沒有). 比如,當一個頁面已經遷移,但是你輸入原來的網站地址的時候就會發生。 或者, 比如example.com -> m.example.com/home 。 這樣耗費的時間成本是雙倍的。 這裏就會經過兩次DNS解析,TCP連接,以及請求的發送。所以,在後臺設置好正確的網址是很重要的。 如圖:

這裏,我們可以使用.performance的屬性,計算出重定向時間

redirectTime = redirectEnd - redirectStart

接著我們就到了cache,DNS,TCP,Request,以及Response的階段 ##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.

process,onload

這裏的過程其實就和開頭的時候說的一樣 (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 ready事件淺析

jquery主要做的工作就是兼容IE6,7,8實現DOMContentLoaded的效果.由於現在主流只要兼容到IE8, 剩下IE6,7我們不做過多的分析了。 目前流行的做法有兩種, 一種是使用readystatechange實現,另外一種使用IE自帶的doScroll方法實現.

readyStateChange

這其實是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方式

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~


Tags: 加載 頁面 一個 就是 quot 詳述

文章來源:


ads
ads

相關文章
ads

相關文章

ad