跳至正文

🌐 瀏覽器如何運作?

📖 定義

Web瀏覽器是解析HTML、CSS、JavaScript檔案並向使用者顯示視覺化網頁的軟體。瀏覽器經歷從伺服器接收的程式碼進行解析、建立DOM樹、計算樣式、構建版面,然後渲染到螢幕的複雜過程。

🎯 用比喻理解

餐廳廚房

將瀏覽器比作餐廳廚房:

訂單單(HTML)          → 廚師閱讀
├─ 「牛排」
├─ 「沙拉」
└─ 「紅酒」

食譜(CSS) → 決定烹飪風格
├─ 牛排:五分熟、鹽、胡椒
├─ 沙拉:香醋醬
└─ 紅酒:紅葡萄酒、冰鎮

特別指示(JavaScript) → 動態變化
├─ 「如果客人改變熟度」
├─ 「去除過敏食材」
└─ 「可以追加訂單」

完成的菜餚(Render) → 上菜給客人

⚙️ 運作原理

1. 瀏覽器的主要元件

瀏覽器
├─ UI(使用者介面)
│ └─ 網址列、書籤、前進/後退按鈕

├─ 瀏覽器引擎
│ └─ 控制UI和渲染引擎之間的動作

├─ 渲染引擎
│ ├─ Blink(Chrome、Edge)
│ ├─ WebKit(Safari)
│ └─ Gecko(Firefox)

├─ JavaScript引擎
│ ├─ V8(Chrome、Node.js)
│ ├─ SpiderMonkey(Firefox)
│ └─ JavaScriptCore(Safari)

├─ 網路
│ └─ HTTP請求/回應處理

├─ UI後端
│ └─ 呼叫OS的UI方法

└─ 資料儲存
└─ Cookie、localStorage、IndexedDB

2. 渲染流程(Critical Rendering Path)

1. HTML解析 → DOM樹生成
<html>
<body>
<h1>Title</h1>
</body>
</html>

→ DOM Tree:
html
└─ body
└─ h1 ("Title")

2. CSS解析 → CSSOM樹生成
h1 { color: blue; font-size: 24px; }

→ CSSOM Tree:
h1
├─ color: blue
└─ font-size: 24px

3. JavaScript執行
- DOM操作
- 事件處理
- 非同步任務

4. 渲染樹生成(DOM + CSSOM)
- 只包含螢幕上可見的元素
- 排除display: none

5. 版面(Reflow)
- 計算每個元素的精確位置和大小

6. 繪製(Paint)
- 在螢幕上繪製像素

7. 合成(Composite)
- 合併多個圖層生成最終畫面

💡 實際範例

HTML解析過程

<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css"> <!-- 1. CSS下載開始 -->
<script src="script.js"></script> <!-- 2. JS下載和執行(阻塞!) -->
</head>
<body>
<h1>Hello World</h1> <!-- 3. DOM生成 -->
<script>
// 4. 內嵌指令碼執行(阻塞!)
console.log('Loaded');
</script>
</body>
</html>

<!-- 問題:JavaScript阻塞HTML解析 -->

<!-- ✅ 解決:非同步載入 -->
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<!-- defer: HTML解析完成後執行 -->
<script defer src="script.js"></script>

<!-- async: 下載完成立即執行 -->
<script async src="analytics.js"></script>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>

DOM操作和效能

// ❌ 低效:多次reflow
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = i;
document.body.appendChild(div); // 1000次reflow!
}

// ✅ 高效:只有一次reflow
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
const div = document.createElement('div');
div.textContent = i;
fragment.appendChild(div);
}
document.body.appendChild(fragment); // 1次reflow

// ✅ 更好:innerHTML(瀏覽器最佳化)
const html = Array.from({ length: 1000 }, (_, i) =>
`<div>${i}</div>`
).join('');
document.body.innerHTML = html;

最小化Reflow和Repaint

// Reflow(版面重新計算 - 成本高)
// - 位置、大小變化
element.style.width = '100px';
element.style.height = '100px';
element.style.margin = '10px';

// Repaint(重新繪製 - 成本低)
// - 顏色、背景變化
element.style.color = 'red';
element.style.backgroundColor = 'blue';

// ❌ 低效:多次reflow
element.style.width = '100px'; // reflow
element.style.height = '100px'; // reflow
element.style.padding = '10px'; // reflow

// ✅ 高效:使用類別一次性修改
element.className = 'box'; // 1次reflow

// CSS
.box {
width: 100px;
height: 100px;
padding: 10px;
}

// ✅ 更好:使用transform(無reflow)
element.style.transform = 'translateX(100px)'; // GPU加速
element.style.opacity = 0.5; // GPU加速

JavaScript事件迴圈

console.log('1. 同步程式碼開始');

setTimeout(() => {
console.log('2. Timeout');
}, 0);

Promise.resolve().then(() => {
console.log('3. Promise');
});

console.log('4. 同步程式碼結束');

// 輸出順序:
// 1. 同步程式碼開始
// 4. 同步程式碼結束
// 3. Promise(Microtask Queue)
// 2. Timeout(Task Queue)

// 事件迴圈運作原理
/*
Call Stack(正在執行的函式)

Microtask Queue(高優先權)
- Promise.then
- MutationObserver

Task Queue(Macrotask)
- setTimeout
- setInterval
- I/O
*/

資源載入最佳化

<!DOCTYPE html>
<html>
<head>
<!-- 1. Preconnect: DNS、TLS預連線 -->
<link rel="preconnect" href="https://fonts.googleapis.com">

<!-- 2. DNS Prefetch: 僅DNS預解析 -->
<link rel="dns-prefetch" href="https://api.example.com">

<!-- 3. Preload: 預載入重要資源 -->
<link rel="preload" href="font.woff2" as="font" crossorigin>

<!-- 4. Prefetch: 預載入稍後需要的資源 -->
<link rel="prefetch" href="next-page.html">

<!-- 5. 內嵌關鍵CSS -->
<style>
/* 初始渲染所需的CSS */
body { margin: 0; font-family: sans-serif; }
.hero { height: 100vh; }
</style>

<!-- 6. 其餘CSS非同步載入 -->
<link rel="stylesheet" href="main.css" media="print" onload="this.media='all'">
</head>
<body>
<!-- 7. 圖片延遲載入 -->
<img src="hero.jpg" alt="Hero" loading="eager">
<img src="image1.jpg" alt="Image 1" loading="lazy">

<!-- 8. JavaScript放在最後或使用defer -->
<script defer src="main.js"></script>
</body>
</html>

效能測量

// Performance API
const perfData = performance.getEntriesByType('navigation')[0];

console.log('DNS查詢:', perfData.domainLookupEnd - perfData.domainLookupStart);
console.log('TCP連線:', perfData.connectEnd - perfData.connectStart);
console.log('TTFB:', perfData.responseStart - perfData.requestStart);
console.log('DOM載入:', perfData.domContentLoadedEventEnd - perfData.domContentLoadedEventStart);
console.log('總載入:', perfData.loadEventEnd - perfData.loadEventStart);

// Core Web Vitals測量
// 1. LCP(Largest Contentful Paint)
new PerformanceObserver((list) => {
const entries = list.getEntries();
const lastEntry = entries[entries.length - 1];
console.log('LCP:', lastEntry.renderTime || lastEntry.loadTime);
}).observe({ type: 'largest-contentful-paint', buffered: true });

// 2. FID(First Input Delay)
new PerformanceObserver((list) => {
const entries = list.getEntries();
entries.forEach(entry => {
console.log('FID:', entry.processingStart - entry.startTime);
});
}).observe({ type: 'first-input', buffered: true });

// 3. CLS(Cumulative Layout Shift)
let cls = 0;
new PerformanceObserver((list) => {
list.getEntries().forEach(entry => {
if (!entry.hadRecentInput) {
cls += entry.value;
}
});
console.log('CLS:', cls);
}).observe({ type: 'layout-shift', buffered: true });

🤔 常見問題

Q1. 為什麼不同瀏覽器的渲染不同?

A: 因為每個瀏覽器使用不同的渲染引擎:

Chrome/Edge → Blink
Safari → WebKit
Firefox → Gecko

解決方案:
1. 遵守Web標準
2. 跨瀏覽器測試
3. 使用Polyfill
4. CSS Reset/Normalize

Q2. 什麼是DOM?

A: Document Object Model - 可以用JavaScript操作HTML的樹結構:

<html>
<body>
<h1 id="title">Hello</h1>
<p class="text">World</p>
</body>
</html>
// 透過DOM API存取
document.getElementById('title').textContent = 'Hi';
document.querySelector('.text').style.color = 'red';

// DOM樹
document
└─ html (document.documentElement)
└─ body (document.body)
├─ h1#title
└─ p.text

Q3. Virtual DOM為什麼更快?

A: 因為它最小化了實際的DOM操作:

// ❌ 實際DOM:3次reflow
element.style.width = '100px';
element.style.height = '100px';
element.textContent = 'New';

// ✅ Virtual DOM(React等)
// 1. 在記憶體中計算變化
// 2. 只將差異套用到實際DOM
// 3. 只有一次reflow!

// React範例
function MyComponent() {
const [count, setCount] = useState(0);

// count變化時
// 1. 首先在Virtual DOM中比較
// 2. 只更新實際變化的部分
return <div>{count}</div>;
}

Q4. 瀏覽器快取如何運作?

A:

透過HTTP回應標頭控制:

1. Cache-Control
Cache-Control: max-age=3600 # 快取1小時
Cache-Control: no-cache # 每次驗證
Cache-Control: no-store # 不快取

2. ETag(變化偵測)
回應: ETag: "abc123"
請求: If-None-Match: "abc123"
→ 無變化則304 Not Modified

3. Last-Modified
回應: Last-Modified: Wed, 21 Oct 2024 07:28:00 GMT
請求: If-Modified-Since: Wed, 21 Oct 2024 07:28:00 GMT
→ 無變化則304
// 使用Service Worker控制快取
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request).then(response => {
// 快取中有則返回,否則發起網路請求
return response || fetch(event.request);
})
);
});

Q5. 瀏覽器渲染最佳化技巧?

A:

// 1. CSS放在head中,JS放在body末尾
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<!-- content -->
<script src="script.js"></script>
</body>

// 2. 動畫使用transform/opacity
// ❌ 慢:width、height、margin(reflow)
element.style.width = '200px';

// ✅ 快:transform(GPU加速)
element.style.transform = 'scaleX(2)';

// 3. 使用requestAnimationFrame
function animate() {
// 動畫程式碼
requestAnimationFrame(animate); // 60fps執行
}
requestAnimationFrame(animate);

// 4. 使用Intersection Observer延遲載入
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.src = entry.target.dataset.src;
observer.unobserve(entry.target);
}
});
});

document.querySelectorAll('img[data-src]').forEach(img => {
observer.observe(img);
});

// 5. 使用防抖/節流最佳化事件
function debounce(func, wait) {
let timeout;
return function(...args) {
clearTimeout(timeout);
timeout = setTimeout(() => func.apply(this, args), wait);
};
}

window.addEventListener('resize', debounce(() => {
console.log('Resized!');
}, 250));

🎓 下一步

理解瀏覽器運作原理後,可以學習:

  1. Web效能最佳化(文件準備中) - 實戰最佳化技巧
  2. 什麼是React? - Virtual DOM的應用
  3. SEO基礎(文件準備中) - 搜尋引擎最佳化

開發者工具的使用

// Chrome DevTools有用的功能

// 1. Performance選項卡
// - Record按鈕測量效能
// - Lighthouse自動分析

// 2. Network選項卡
// - 查看資源載入時間
// - Throttling模擬慢速網路

// 3. Coverage選項卡
// - 查看未使用的CSS/JS

// 4. Rendering選項卡
// - Paint flashing: 顯示重繪區域
// - Layout Shift Regions: 識別CLS原因

🎬 總結

瀏覽器是複雜但最佳化的系統:

  • 解析:HTML、CSS轉換為樹結構
  • 渲染:DOM + CSSOM → 顯示到螢幕
  • JavaScript引擎:程式碼執行和事件處理
  • 最佳化:最小化Reflow、非同步載入、快取

理解瀏覽器的運作原理可以建立更快更高效的網站! 🌐✨