位元詩人 [網頁設計] 教學:網頁載入的過程

Facebook Twitter LinkedIn LINE Skype EverNote GMail Yahoo Email

前言

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

網頁載入的時間軸

網頁載入的觸發時機發生於輸入網址 (URL)、按下超連接 (hyperlink)、送出表單 (form)、觸發 Ajax 連線等。

網頁載入時,會進行一連串的動作。網頁載入的過程如下:

網頁載入的過程

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

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

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

(3) 至 (4) 的過程,就是網頁後端的部分,一般程式人熟知的 PHP、Rails、Node.js 等網頁程式的用途就是在處理這個過程。網頁後端會依據請求回傳一個回應,該回應就是網頁頁面。

預設情境下,單次網頁載入事件只會回傳一次網頁,除非之後再引發一次網頁載入事件,像是傳送表單或觸發 Ajax 事件等。

(5) 至 (7) 的過程,則是網頁前端的部分,瀏覽器收到網頁後,就會開始處理。處理後就會輸出頁面及執行頁面上的 JavaScript 程式。

在前端有兩個重要的時間點,一個是 (6) 的 DOM 載入完成 (DOM ready),對應的是瀏覽器的 Document 物件Document: DOMContentLoaded 事件,一個是 (7) 的頁面輸出完成 (page rendered),對應的是 Window 物件Window: load 事件

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

$(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);

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

觸發 JavaScript 程式的時機

當我們了解網頁載入的過程後,我們可以來看 JavaScript 程式觸發的時機點。

一般來說,依照程式觸發的時機,可將 JavaScript 程式分為三種:

  • 立即執行
  • 事件處理器 (event handler)
  • 預先寫好的程式碼區塊,像是函式或物件

第一種 JavaScript 程式在網頁載入到該行時會立即執行,但只會執行一次,之後不會再執行,除非重刷頁面。

第二種程式會在載入時註冊事件處理器到網頁元素上,之後使用者和該網頁元素互動時,即會觸發相對應的 JavaScript 程式。

第三種程式不會自主觸發,而要由前兩種程式來呼叫。市面上可見的 JavaScript 函式庫或框架,都是預先寫好的程式碼區塊。載入頁面後再由程式設計師所寫的程式來呼叫。

了解 JavaScript 程式的觸發時機對寫網頁程式來說相當重要。有時候我們會困惑於某段該觸發而未觸發的 JavaScript 程式碼,這時候可以檢查看看該段 JavaScript 程式碼是否有正確觸發。

優化網頁載入

一般來說,我們會將 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 的建議網頁起始模板 (參考https://getbootstrap.com/docs/4.1/getting-started/introduction/#starter-template[這裡]):

<!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> 所在的位置。

非同步載入 CSS 程式碼

在預設情形下載入 CSS 的過程是同步性、阻塞性的。我們可以使用非同步載入的方式加速頁面載入的過程,但不是每個瀏覽器都支援得麼好 (參考https://caniuse.com/#feat=link-rel-preload[這裡]),所以需加入額外的 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,避免載入失敗。

非同步載入 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.jsapp.js

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

關於作者

身為資訊領域碩士,位元詩人 (ByteBard) 認為開發應用程式的目的是為社會帶來價值。如果在這個過程中該軟體能成為永續經營的項目,那就是開發者和使用者雙贏的局面。

位元詩人喜歡用開源技術來解決各式各樣的問題,但必要時對專有技術也不排斥。閒暇之餘,位元詩人將所學寫成文章,放在這個網站上和大家分享。