讀書的隨手筆記與延伸問題
2.1 生命週期概述
瀏覽器產生網頁時可分成兩個階段
- 建立頁面階段
- 事件處理階段
2.2 頁面建立階段
- 解析 HTML 並建立 DOM
- 執行 JavaScript 程式
瀏覽器在頁面建立階段時,會根據需求在步驟 1 與 步驟 2 之間切換許多次
問:在第二階段時,已建立完的 DOM 會顯示在瀏覽器上嗎?
答:會
<h1>First</h1>
<script>
var i = 0; // 此時 First 的文字會顯示於頁面
</script>
<h1>Two</h1>
<script>
console.log(i); // 此時 Two 的文字會顯示於頁面
</script>
<h1>Three</h1>
2.2.1 頁面建立階段
遇到 script element 時,會暫停解析 HTML,開始執行 JavaScript 程式。
瀏覽器可修復在 HTML 上發現的問題,以建立一個有效的 DOM。
工作到現在,有時候在討論的過程中,有些人會講 element,有些人會講 tag,決定要好好來個名詞解釋
An HTML element is defied by a start tag, some content, and an end tag.
<script> <!-- start tag -->
function() {}; // content
</script> <!-- end tag -->
<h1> <!-- start tag -->
Content <!-- content -->
</h1> <!-- end tag -->
期望自己以後要講的明確 XD
2.2.2 執行 JavaScript 程式
JavaScript 核心 (以 ECMAScript 標準為基礎)
- BOM (Browser Object Model,瀏覽器物件模型)
- DOM (Document Object Model,文件物件模型)
BOM 就是 window
DOM 就是 window.document
在頁面建立階段,JavaScript 無法選取與修改尚未建立的 element,這就是為什麼傾向把 script 放在 body 的最後,確保所有的 element 都解析並建立成 DOM
在頁面建立階段,JavaScript 會維持他的全域狀態
<h1>Hi</h1>
<script>
var iStillLive = 'love';
</script>
<h1>Hello</h1>
<script>
console.log(iStillLive); // 'love'
</script>
問:那變數定義在不同的 Script element 之間,也會有 hoisting 的情形嗎?
下列範例的輸出結果為何?
<script>
console.log(foo);
foo = false;
console.log(foo);
</script>
<script>
var foo = true;
console.log(foo);
</script>
Ans:
ReferenceError
個人覺得這種情境很有趣,可能是因為瀏覽器在執行第一個 script element 的程式時,會暫停 DOM 的建置,所以第二個 script 沒有被建置與執行。
這也是為什麼我們在使用 plugin 時,必須要注意載入 script 的先後順序。
Example:
<!-- jQuery -->
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<!-- FlexSlider -->
<script defer src="js/jquery.flexslider.js"></script>
2021/09/13 update
原因:當執行第一個 script 時,會建立 Execution Context( Global Executon Context ),但此時不包含第二個 script 的程式內容,因此在建立時期就沒有配置 foo 這個變數,所以會造成 Error。
如果把第一個 console.log 移除,會發生什麼事情呢?
<script>
foo = false;
console.log(foo);
</script>
<script>
var foo = true;
console.log(foo);
</script>
Ans:
false
true
咦?為什麼這時候沒有出現錯誤?
其實是因為對一個尚未宣告的變數賦值,該變數會隱含的被宣告成一個全域變數,此時 foo = false
就可以正常運行
延伸閱讀:
變數已宣告跟未宣告的差異
2.3 事件處理
2.3.1 事件處理概述
瀏覽器執行環境的核心是基於一次只能執行一段程式碼的觀念,也就是單一執行緒執行模型(single-threaded execution model)
The browser execution environment is, at its core, based on the idea that only a single piece of code can be executed at once: the so-called single-threaded execution model.
情境:銀行只有一個窗口,一次只能處理一個客人,每個客人都要排隊。
使用者產生的事件或是伺服器產生的事件,都會按照順序放到 event queue,由瀏覽器進行檢測
將事件放入 event queue 的瀏覽器工作機制,不在頁面建立與事件處理的執行緒之中
問:瀏覽器有幾個 process 與 thread 在運行?
答:同時有多個 process 在運行
- Browser process:主控瀏覽器相關,像是書籤功能、發出網路請求等
- UI thread
- Network thread
- Renderer process:頁面顯示
- Main thread
- Worker thread
- Compoisitor thread
- Raster thread
- Plugin process:控制網站所用的 plugin,例如:flash
- GPU process:跨核心的工作與 3D 圖形繪製相關等
每個 tab 擁有自己獨立運作的(renderer)process。
每個 iframe 是由獨立運作的 renderer process 來負責執行
Browser process 與 Renderer process 之間透過 IPC ( inter process communication ) 進行溝通
事件是非同步的
事件類型
- 使用者事件:點擊、鍵盤輸入、移動滑鼠
- 瀏覽器事件:網頁處於已載入或未載入的狀態
- 網路事件:來自伺服器的回應 ( ajax )
- 計時器事件:setTimeout 或 setInterval
事件處理的概念是 Web 應用程式的核心,絕大多數的程式碼都將作為某些事件的結果而被執行
2.3.2 註冊事件處置器
註冊事件的方式
- 將函式指定給特殊屬性
- 使用內建的 addEventListener 方法
window.onload = function() {}; // 將 function 指定給 onload 屬性
document.body.onclick = function() {}; // 將 function 指定給 onload 屬性
// 缺點,某一個特定事件只能指派一個事件處置器
// It’s only possible to register one function handler for a particular event.
// 容易覆蓋掉之前的事件處理函式
document.body.addEventListener('mousemove', function() {});
document.body.addEventListener('click', function() {});
事件迴圈 ( Event Loop )會一直執行,直到使用者關閉 Web 應用程式
References
從內部來看瀏覽器到底在做什麼?
重學瀏覽器(1)-多程式多執行緒的瀏覽器
Alfredo's Note
Austin's Note