網頁程式設計教學:網頁載入的過程

PUBLISHED ON DEC 4, 2018 — WEB

    在本文中,我們會介紹網頁載入的過程。這個過程不會顯示在程式碼中,但我們要對這個過程有概念,因為網頁載入的過程會影響網頁程式的行為,有時候程式的問題不在於程式碼本身的問題,而是沒有弄清楚網頁載入的過程。

    網頁載入的時間軸

    網頁載入的時機發生於輸入網址 (URL)、按下連接 (hyperlink)、送出表單 (form) 等。載入的過程如下:

    網頁載入的過程

    我們將其抓出幾個重要的時間點:

    1. 發出網頁載入事件
    2. 經 DNS 查詢到實際的主機位置
    3. 向網頁程式發出請求 (request)
    4. 網頁程式處理請求後給予回應 (response)
    5. 瀏覽器收到網頁
    6. 瀏覽器解析網頁
    7. 瀏覽器輸出網頁

    我們假定使用者可順利存取我們的網頁程式,著重在 (3) 至 (7) 的過程。

    (3) 至 (4) 的過程,就是網頁後端的部分,一般程式人熟知的 PHP、Rails、Node.js 等網頁程式的用途就是在處理這個過程。網頁後端會依據請求回傳一個回應,該回應就是網頁。預設情境下,單次網頁載入事件只會回傳一次網頁,除非之後再引發一次網頁載入事件,像是傳送表單或觸發 Ajax 事件等。

    (5) 至 (7) 的過程,則是網頁前端的部分,瀏覽器收到網頁後,就會開始處理。在前端有兩個重要的時間點,一個是 (6) 的 DOM 載入完成 (DOM ready),對應的是瀏覽器的 document 物件的 "OMContentLoaded" 事件,一個是 (7) 的頁面輸出完成 (page rendered),對應的是 window 物件的 "load" 事件。

    以下的 jQuery 程式會在 DOM 載入完成後觸發:

    $(document).ready(function () {
        // Implement your code here.
    });
    

    像是要將網頁進行繁簡中文轉換,應該要在網頁上所有的文字都載入後才進行,就會將程式碼放在這個區塊。

    如果不想用 jQuery,以下是 $(document).ready 相對應的原生 JavaScript 程式碼:

    document.addEventListener('DOMContentLoaded', fn, false);
    

    以下的 jQuery 程式則會在頁面輸出完成後觸發:

    $(window).load(function () {
        // Implement your code here.
    });
    

    例如,我們想偵測 Google AdSense 廣告是否被阻擋掉,由於 AdSense 廣告使用非同步載入,我們應該在整個頁面都載入後才檢查。

    如果不想用 jQuery,以下是 $(window).load 相對應的原生 JavaScript 程式碼:

    window.addEventListener('load', fn, false);
    

    有時候某段程式碼看似正常,卻沒有正常地觸發,這時候可以想一想程式實際會在時間軸的那個時間點觸發,或是根本沒有觸發,應該就可以順利地修掉臭蟲。

    優化網頁載入

    一般來說,我們會將 CSS 載入標籤放在 <head> 中而把 JavaScript 載入標籤放在 <body> 的尾端:

    <!DOCTYPE html>
    <head>
      <!-- Some metadata -->
      
      <!-- Load CSS here -->
    </head>
    <body>
    
      <!-- Load JavaScript here -->
    </body>
    

    這是因為在網頁載入的過程中,我們會希望讓使用者儘早看到頁面的內容,但使用者可等網頁完全載入後再開始操作頁面。<link><script> 載入資源的行為是阻塞性的 (blocking),如果把 <script> 放在網頁的前段,使用者會有較長的時間看到空白的頁面,這對使用者體驗算是減分。

    以下是 Bootstrap 4.x 的建議網頁起始模板 (參考這裡):

    <!doctype html>
    <html lang="en">
      <head>
        <!-- Required meta tags -->
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    
        <!-- Bootstrap CSS -->
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
    
        <title>Hello, world!</title>
      </head>
      <body>
        <h1>Hello, world!</h1>
    
        <!-- Optional JavaScript -->
        <!-- jQuery first, then Popper.js, then Bootstrap JS -->
        <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.3/umd/popper.min.js" integrity="sha384-ZMP7rVo3mIykV+2+9J3UJ46jBk0WLaUAdn689aCwoqbBJiSnjAK/l8WvCWPIPm49" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
      </body>
    </html>
    

    可以觀察到也是該頁面按照這個思維擺放 <link><script> 所在的位置。

    非同步載入 JavaScript 程式碼

    在預設情形下,載入 JavaScript 原始碼的過程是同步、阻塞性的,即使按照前一節的方法載入這些資源,仍然無法縮短時間。HTML5 在 <script> 標籤中引入 asyncdefer 兩個屬性,藉由非同步載入來縮短網頁載入時間。

    這兩者有一些差別:

    • async:該 JavaScript 程式會以非同步模式下載和執行
    • defer:該 JavaScript 會以非同步的方式來下載,在網頁載入 (DOM ready) 後同步執行
    • async defer:該 JavaScript 會以非同步的方式來下載,在網頁載入 (DOM ready) 後以非同步模式執行

    像 jQuery 就可以用 defer,但不適合 async。我們假定 app.js 有用到 jQuery,代碼如下:

    <script src="path/to/jquery.min.js" async></script>
    <script src="path/to/app.js" async></script>
    

    在這個時候,我們無法保證 app.js 執行時,jQuery 已經下載完畢,有可能造成程式失效。

    我們將程式碼修改如下:

    <script src="path/to/jquery.min.js" defer></script>
    <script src="path/to/app.js" defer></script>
    

    這時候,頁面會在背景下載 JavaScript 命令稿,在網頁載入後依序執行 jquery.min.js 和 *app.js*。

    由於 asyncdefer 是 HTML5 中新的屬性,如果自己的網頁程式不需支援舊瀏覽器,可以考慮使用這些屬性改善網頁載入速度。

    非同步載入 CSS 程式碼

    同樣地,在預設情形下載入 CSS 的過程是同步、阻塞性的。我們可以使用非同步載入的方式加速頁面載入的過程,但不是每個瀏覽器都支援得麼好 (參考這裡),所以需加入額外的 polyfill

    傳統的 CSS 載入方式如下:

    <link href="path/to/style.css" as="style">
    

    我們將其改用非同步模式載入如下:

    <link rel="preload" href="path/to/style.css" as="style" onload="this.rel='stylesheet'">
    <noscript><link rel="stylesheet" href="path/to/style.css"></noscript>
    

    這裡的重點是加入 rel="preload" 屬性,之後就會用非同步模式載入該 CSS。但載入後不會執行,故再加上 onload="this.rel='stylesheet'",用一小段 JavaScript 讓頁面載入該 CSS。

    由於這種載入方式會用到 JavaScript,故我們額外寫入一段 <noscript> 標籤做為 fallback。

    目前市面上瀏覽器對 rel="preload" 支援不是那麼好,所以需加入相關 polyfill,避免載入失敗。

    TAGS: WEB