iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 2
0
Modern Web

JavaScript 忍者的修練--從下忍進階到中忍系列 第 2

Day 02: 網頁生命週期之頁面建置

一個網頁應用程式的生命週期可分為以下階段:

  1. 輸入網址或點擊超連結
  2. 瀏覽器向伺服器送出 request
  3. 伺服器回傳回應頁面(HTML, CSS, 和 JavaScript 檔案)
  4. 瀏覽器解析回應頁面,建置頁面
  5. 瀏覽器監看 event queue,處理互動事件
  6. 使用者關閉網頁,結束生命週期

瀏覽器這邊主要的工作在 4 和 5 這二個階段,讓我們用下面這個 web app 當範例,看看瀏覽器做了哪些事情。

<!DOCTYPE html>
<html>
	<head>
		<title>Web app lifecycle</title>
	</head>
	<style>
		#first { color: green; }
		#second { color: red; }
	</style>
	<body>
		<ul id="first"></ul>
		<script>
			function addMessage(element, message) {
			  var messageElement = document.createElement("li");
			  messageElement.textContent = message;
			  element.appendChild(messageElement);
			}
			var first = document.getElementById("first");
			addMessage(first, "Page loading");
		</script>
		<ul id="second"></ul>
		<script>
			document.body.addEventListener("mousemove", function() {
				var second = document.getElementById("second");
				addMessage(second, "Event: mousemove");
			});
			document.body.addEventListener("click", function() {
				var second = document.getElementById("second");
				addMessage(second, "Event: click");
			});
		</script>
	</body>
</html>

在這個簡單的 web app 有二個<ul>元素,id分別為firstsecond,並用不同的 CSS 規則上色區分。

第一段 JavaScript 定義一個 addMessage 函式,會在給定的元素裡新增一個<li> 元素並塞一段訊息文字。

在網頁載入時我們取得 #first元素,將訊息文字附加在其中。

第二段 JavaScript 在頁面建立二個 event listeners,分別監聽 mousemoveclick事件,事件發生時在 #second裡增加對應的訊息文字。

https://ithelp.ithome.com.tw/upload/images/20190904/20091606JZJyZsjBEE.png

讓我們先從頁面建置階段開始看,在這個階段瀏覽器主要做了二件事:

  1. 解析 HTML 並建立 DOM
  2. 執行 JavaScript 程式

解析 HTML 並建立 DOM

HTML 是由不同的元素(element)組成一個樹狀結構資料,瀏覽器會一一解析這些元素並建立對應的 DOM (Document Object Model 文件物件模型)節點,DOM 建好後瀏覽器會按照這個模型建立頁面。

HTML 元素是一組由相同名稱的標籤組合而成的資料結構。

An HTML element
(圖片來源MDN

DOM 是 HTML 的結構化表示形式,其中每個 HTML 元素都表示為一個節點(node)。上一層節點稱為 parent node,下一層節點叫做 child node,每個節點一定會有一個 parent node,除了最外層的根節點<html>之外。一個節點可以有任何數量的 child nodes,擁有同樣 parent node 的節點彼此之間屬於 sibling nodes。

https://ithelp.ithome.com.tw/upload/images/20190904/200916062XeJ5ebHbT.jpg

在我們的範例裡,<head><body><html>的 child nodes;<html>是它們的 parent node;<head><body>互相是對方的 sibling node。

我們常把節點也叫做元素,實際上元素是節點的一種,只不過在 DOM 裡面為數眾多,而且我們操作 DOM 經常是在操作元素,所以會常常在說明文件裡看到這二個名詞互相替代

雖然說 DOM 是參考 HTML 建立的,但二者是不同的東西,我們可以把 HTML 當做建築物的藍圖,DOM 是建築物的主要結構,這樣子比較容易分辨。而且在建立 DOM 的過程中,瀏覽器有辦法修正 HTML 的問題,建立有效的 DOM。讓我們看一個例子。

<html>
	<head>
		<p>hello</p>
	</head>
	<body>
	</body>
</html>

在這個錯誤的 HTML 裡,我們不小心把<p>放到<head>裡面,照這樣產生的 DOM,p節點會是head的 child node。

但是我們看到瀏覽器的頁面,<p>卻是出現在<body>裡,也就是說瀏覽器發現 HTML 的錯誤,修正 DOM 把p修正為body的 child node。

https://ithelp.ithome.com.tw/upload/images/20190904/20091606EYFweJmrhi.jpg

當遇到 <script>這個特殊元素時,瀏覽器會停止解析 DOM 並切換到步驟二,執行裡面的 JavaScript 程式。

執行 JavaScript 程式

瀏覽器提供了一些操作介面讓 JavaScript 能與頁面互動,包含一個全域window物件以及瀏覽器 API。

全域的 window 物件,代表頁面所屬的瀏覽器視窗,可藉由它來存取所有其他的全域物件、全域變數及瀏覽器API。這個物件會一直存在,直到頁面關閉。

window之中最重要的屬性是document,也是目前頁面的 DOM 結構,透過document,JavaScript 程式碼可以任意改變所在頁面的 DOM 結構,像是修改或刪除現有元素,甚至是建立及插入新的元素。

例如範例中的一段程式碼:

var first = document.getElementById("first");

我們用全域物件window中的document取得目前頁面的 DOM 結構,在其中找出具有id="first"的元素,然後把它指派給first變數。

運用window裡的屬性或方法時,window可以省略不寫。

JavaScript 引擎會從第一行開始一行一行的執行程式碼,在函式裡的程式碼要等到函式被呼叫後才會執行。

此時 JavaScript 程式碼可以任意改變現有的 DOM 結構,我們在取得了first元素後,呼叫addMessage函式,並把first和一段文字訊息當參數代入,此時開始執行函式裡的程式碼,先建立一個li元素,將代入的文字訊息指派給元素的textContent屬性,最後再將這個新元素附加在代入的first元素之下,成為它的 child node。

https://ithelp.ithome.com.tw/upload/images/20190904/20091606jPwWVokqpA.jpg
(執行script裡的程式碼後,新的 DOM 結構)

雖然說 JavaScript 可以任意變動 DOM,但它無法存取尚未建立的節點,像是在第一段<script>裡要存取#second會得到null的值,因為這個時候它還沒有出現在 DOM 裡。這也是我們會把<script>放在頁面底部的緣故,因為這樣就可以確保程式裡的 HTML 元素都已經在 DOM 裡了。

執行完<script>裡最後一行程式碼後,瀏覽器會回到解析剩下的 HTML 繼續建立 DOM,如果遇到新的 <script>元素會再重複相同過程。

這裡的重點是在script元素中建立的全域變數,會一直保持全域的狀態,因為全域變數是依附在window之下,而window在頁面生命週期結束前一直都是有效的。

下一篇文章繼續看生命週期的第二階段: 事件處理。


上一篇
Day 01: JavaScript 忍者修練--前言
下一篇
Day 03: 網頁生命週期之事件處理
系列文
JavaScript 忍者的修練--從下忍進階到中忍30

尚未有邦友留言

立即登入留言