瀏覽器的工作本質上是:把一個 URL 變成螢幕上顯示的網頁。理解這個過程對於效能最佳化和錯誤排查有很大幫助。
瀏覽器渲染流程#
flowchart TD
A[URL] --> B[HTTP 請求]
B --> C[HTML 解析]
C --> D[DOM 樹]
D --> E[CSS 計算]
E --> F[CSSOM]
F --> G[渲染樹]
G --> H[佈局 Layout]
H --> I[繪製 Paint]
I --> J[合成 Composite]
J --> K[顯示到螢幕]
style A fill:#e3f2fd
style K fill:#c8e6c9這個過程是流式處理的:不需要等到上一步完全結束,就開始處理上一步的輸出。這就是為什麼我們會看到網頁逐步出現。
HTTP 協定#
請求結構#
GET /path HTTP/1.1 ← Request Line
Host: example.com ← Headers
Content-Type: application/json
← 空行
{ "key": "value" } ← Body回應結構#
HTTP/1.1 200 OK ← Response Line
Content-Type: text/html ← Headers
Content-Length: 1234
← 空行
<!DOCTYPE html>... ← BodyHTTP 方法#
| 方法 | 用途 |
|---|---|
| GET | 獲取資源(瀏覽器地址欄) |
| POST | 提交資料(表單提交) |
| PUT | 新增/替換資源 |
| DELETE | 刪除資源 |
| HEAD | 只獲取回應頭 |
| OPTIONS | 預檢請求(CORS) |
HTTP 狀態碼#
| 範圍 | 含義 | 常見狀態碼 |
|---|---|---|
| 1xx | 臨時回應 | 100 Continue |
| 2xx | 成功 | 200 OK |
| 3xx | 重定向 | 301 永久重定向, 302 臨時重定向, 304 未修改 |
| 4xx | 用戶端錯誤 | 400 Bad Request, 403 Forbidden, 404 Not Found |
| 5xx | 服務端錯誤 | 500 Internal Error, 503 Service Unavailable |
304 Not Modified 是前端必知必會的狀態碼。當用戶端有快取版本,且服務端確認沒有更新時,回傳不含 body 的 304 回應。
HTTPS#
HTTPS = HTTP + TLS 加密層
- 身份驗證:確認服務端身份
- 資料加密:防止中間人竊聽或篡改
HTTP/2 改進#
- 服務端推送:提前推送資源到用戶端快取
- TCP 連線複用:多個 HTTP 請求共用一個 TCP 連線
- 頭部壓縮:使用 HPACK 演算法壓縮
DOM 樹構建#
HTML 解析過程:
字節流 → 字符流 → 詞法分析(Token) → 語法分析 → DOM 樹解析範例#
<html>
<head>
<title>Example</title>
</head>
<body>
<p>Hello</p>
</body>
</html>graph TD
DOC[Document] --> HTML[html]
HTML --> HEAD[head]
HTML --> BODY[body]
HEAD --> TITLE[title]
TITLE --> T1["Example"]
BODY --> P[p]
P --> T2["Hello"]
style DOC fill:#e1f5fe
style T1 fill:#fff9c4
style T2 fill:#fff9c4CSSOM#
CSS Object Model 是 CSS 的物件表示,與 DOM 類似但獨立。
獲取元素樣式#
// 獲取計算後的樣式
const styles = window.getComputedStyle(element);
const width = styles.width;
// 獲取元素位置
const rect = element.getBoundingClientRect();
// rect: { x, y, width, height, top, right, bottom, left }
重排與重繪#
重排(Reflow/Layout)#
元素的幾何屬性變化會觸發重排:
- 添加/刪除可見的 DOM 元素
- 元素位置、尺寸變化
- 內容變化(如文字數量)
- 頁面首次渲染
- 瀏覽器視窗大小變化
重繪(Repaint)#
元素外觀變化但不影響佈局時觸發重繪:
- 顏色變化
- 背景變化
- 可見性變化
重排比重繪的代價更高,因為重排必然導致重繪,但重繪不一定導致重排。
最佳化建議#
// 不好:多次重排
element.style.width = "100px";
element.style.height = "100px";
element.style.margin = "10px";
// 好:一次性修改
element.style.cssText = "width: 100px; height: 100px; margin: 10px;";
// 或使用 class
element.className = "new-styles";更多最佳化技巧
批量 DOM 操作
// 使用 DocumentFragment const fragment = document.createDocumentFragment(); items.forEach((item) => { const li = document.createElement("li"); li.textContent = item; fragment.appendChild(li); }); list.appendChild(fragment);
2. **離線 DOM 操作**
```javascript
element.style.display = "none";
// 進行多次 DOM 操作
element.style.display = "block";避免強制同步佈局
// 不好:讀寫交錯 element.style.width = "100px"; const width = element.offsetWidth; // 強制同步佈局 element.style.height = width + "px"; // 好:先讀後寫 const width = element.offsetWidth; element.style.width = "100px"; element.style.height = width + "px";
</div></details>
## 事件機制
### 捕獲與冒泡
> [!IMPORTANT]
>
> **捕獲是計算機處理事件的邏輯,冒泡是人類理解事件的邏輯。**
當你點擊一個按鈕時:
- 滑鼠只知道座標位置
- 瀏覽器需要找出這個座標對應哪個元素 → **捕獲過程**(從外到內)
- 同時,點擊按鈕也意味著點擊了按鈕所在的容器 → **冒泡過程**(從內到外)
### 事件傳播順序
```html
<body>
<div id="outer">
<button id="inner">Click</button>
</div>
</body>// 捕獲階段(第三個參數為 true)
document.body.addEventListener(
"click",
() => {
console.log("body capture");
},
true
);
document.getElementById("outer").addEventListener(
"click",
() => {
console.log("outer capture");
},
true
);
// 冒泡階段(第三個參數為 false 或省略)
document.getElementById("inner").addEventListener("click", () => {
console.log("inner");
});
document.getElementById("outer").addEventListener("click", () => {
console.log("outer bubble");
});
document.body.addEventListener("click", () => {
console.log("body bubble");
});
// 點擊 button 的輸出順序:
// body capture → outer capture → inner → outer bubble → body bubble
sequenceDiagram
participant body
participant outer as outer div
participant inner as inner button
Note over body,inner: 捕獲階段(外 → 內)
body->>outer: body capture
outer->>inner: outer capture
Note over inner: 目標階段
inner->>inner: inner (target)
Note over body,inner: 冒泡階段(內 → 外)
inner->>outer: outer bubble
outer->>body: body bubbleaddEventListener 參數#
element.addEventListener(type, handler, options);
// options 可以是布林值(useCapture)
element.addEventListener("click", handler, true); // 捕獲
element.addEventListener("click", handler, false); // 冒泡(預設)
// 或者是物件
element.addEventListener("click", handler, {
capture: false, // 是否捕獲
once: true, // 只執行一次
passive: true, // 承諾不會呼叫 preventDefault()
});實際開發建議
- 默認使用冒泡模式(不傳第三個參數)
- 開發組件時,需要父元素控制子元素行為時使用捕獲
焦點系統#
焦點系統處理鍵盤事件,整個 UI 中有且僅有一個「聚焦」的元素。
// 設置焦點
element.focus();
// 移除焦點
element.blur();
// Tab 鍵用於切換焦點
// 可以通過 tabindex 屬性控制 Tab 順序
自定義事件#
// 創建自定義事件
const event = new CustomEvent("my-event", {
bubbles: true, // 是否冒泡
cancelable: true, // 是否可取消
detail: { data: "custom data" }, // 自定義資料
});
// 監聽事件
element.addEventListener("my-event", (e) => {
console.log(e.detail.data);
});
// 觸發事件
element.dispatchEvent(event);常用瀏覽器 API#
DOM 操作#
// 查詢
document.getElementById("id");
document.querySelector(".class");
document.querySelectorAll("div");
// 創建
document.createElement("div");
document.createTextNode("text");
document.createDocumentFragment();
// 修改
element.appendChild(child);
element.removeChild(child);
element.replaceChild(newChild, oldChild);
element.insertBefore(newChild, referenceChild);
// 屬性
element.getAttribute("data-id");
element.setAttribute("data-id", "123");
element.removeAttribute("data-id");
// 類名
element.classList.add("active");
element.classList.remove("active");
element.classList.toggle("active");
element.classList.contains("active");位置與尺寸#
// 元素相對於視口的位置
element.getBoundingClientRect();
// 滾動位置
window.scrollX; // 水平滾動距離
window.scrollY; // 垂直滾動距離
// 元素滾動
element.scrollTop;
element.scrollLeft;
// 滾動到指定位置
element.scrollTo({ top: 100, behavior: "smooth" });
element.scrollIntoView({ behavior: "smooth" });Performance API#
// 頁面加載效能
const timing = performance.timing;
const loadTime = timing.loadEventEnd - timing.navigationStart;
// 效能標記
performance.mark("start");
// ... 程式碼執行
performance.mark("end");
performance.measure("duration", "start", "end");
// 獲取測量結果
const measures = performance.getEntriesByType("measure");
console.log(measures[0].duration);總結#
| 概念 | 要點 |
|---|---|
| 渲染流程 | 流式處理,HTML → DOM → CSSOM → 渲染 → 繪製 |
| HTTP | 請求-回應模式,注意狀態碼和快取機制 |
| 重排重繪 | 重排代價高,應批量操作 DOM |
| 事件機制 | 捕獲(外 → 內)→ 冒泡(內 → 外) |
| 焦點系統 | 處理鍵盤事件,Tab 鍵切換 |