概述

浏览器是互联网上使用最广泛的软件之一。理解浏览器内部工作原理对于性能优化、调试和构建高效Web应用至关重要。1

浏览器架构

现代浏览器(如Chrome)采用多进程架构

┌─────────────────────────────────────────────────────┐
│                    Browser Process                   │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐ │
│  │    UI       │  │   Network    │  │   Storage    │ │
│  │   Thread    │  │   Thread     │  │   Thread     │ │
│  └─────────────┘  └─────────────┘  └─────────────┘ │
├─────────────────────────────────────────────────────┤
│              Renderer Process (每个Tab一个)          │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐ │
│  │  JS Engine  │  │   Rendering  │  │   GPU        │ │
│  │  (V8)       │  │   Thread     │  │   Thread     │ │
│  └─────────────┘  └─────────────┘  └─────────────┘ │
└─────────────────────────────────────────────────────┘

渲染引擎

浏览器渲染引擎
Chrome/OperaBlink(WebKit分支)
FirefoxGecko
SafariWebKit
EdgeBlink

渲染管线(Critical Rendering Path)

浏览器将HTML/CSS转换为屏幕上像素的完整流程:

HTML ──▶ DOM ──▶ CSSOM ──▶ Render Tree ──▶ Layout ──▶ Paint ──▶ Composite
         │         │            │            │          │          │
      解析HTML   解析CSS     合并两树     计算位置    绘制像素    合成层

详细步骤

  1. 解析(Parsing):HTML解析为DOM树,CSS解析为CSSOM
  2. 构建渲染树(Render Tree):DOM + CSSOM合并,只包含可见节点
  3. 布局(Layout):计算每个节点的几何信息(位置、大小)
  4. 绘制(Paint):将节点绘制为多个图层(layers)
  5. 合成(Composite):GPU将各图层合成为最终图像

事件循环(Event Loop)

JavaScript是单线程语言,事件循环是其处理异步操作的核心机制。2

核心组件

┌─────────────────────────────────────────────────────────────┐
│                         HEAP                                 │
│              (内存分配,对象存储)                           │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                       CALL STACK                             │
│              (执行代码的调用栈)                             │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                 Web APIs / Node.js APIs                      │
│        (setTimeout, fetch, DOM events, etc.)                 │
└─────────────────────────────────────────────────────────────┘
          │                    │                    │
          ▼                    ▼                    ▼
┌─────────────────┐  ┌─────────────────┐  ┌─────────────────┐
│  Macrotask      │  │  Microtask     │  │  Animation      │
│  Queue          │  │  Queue         │  │  Frame          │
│  (setTimeout,   │  │  (Promise.then,│  │  (rAF)          │
│   setInterval,  │  │   queueMicrotask│  │                 │
│   I/O)          │  │   MutationObs) │  │                 │
└─────────────────┘  └─────────────────┘  └─────────────────┘

执行顺序

console.log('1');  // 同步 → 执行
 
setTimeout(() => console.log('2'), 0);  // 宏任务
 
Promise.resolve().then(() => console.log('3'));  // 微任务
 
console.log('4');  // 同步 → 执行
 
// 输出顺序: 1, 4, 3, 2

规则

  1. 执行所有同步代码,直到栈为空
  2. 执行所有微任务(清空微任务队列)
  3. 执行一个宏任务
  4. 重复

微任务 vs 宏任务

微任务宏任务
Promise.then/catch/finallysetTimeout
queueMicrotask()setInterval
MutationObserversetImmediate(Node)
process.nextTick(Node)I/O回调

requestAnimationFrame

requestAnimationFrame既不是宏任务也不是微任务,它在渲染之前执行:

1. 执行一个宏任务
2. 清空所有微任务
3. 如果需要渲染:
   - 执行 requestAnimationFrame 回调
   - 计算样式(Recalculate Style)
   - 布局(Layout)
   - 绘制(Paint)
   - 合成(Composite)

浏览器的渲染时机

对于60fps(每帧约16.67ms):

┌────────────────────────────────────┐
│  Task → Microtasks → rAF → Layout → Paint  │ ← 16.6ms frame
└────────────────────────────────────┘

渲染优化

回流(Reflow)vs 重绘(Repaint)

触发类型成本
改变元素尺寸、位置Reflow
改变颜色、背景Repaint
使用transform、opacityComposite

避免布局抖动

// ❌ 不好:读写交替,触发多次回流
element.style.width = element.offsetWidth + 10 + 'px';
element.style.height = element.offsetHeight + 10 + 'px';
 
// ✅ 好:批量读,批量写
const width = element.offsetWidth;
const height = element.offsetHeight;
element.style.width = width + 10 + 'px';
element.style.height = height + 10 + 'px';

合成器友好属性

仅触发合成的属性(不触发Layout/Paint):

  • transform
  • opacity
  • filter(部分)
/* ✅ 动画使用transform */
@keyframes slide {
    from { transform: translateX(0); }
    to { transform: translateX(100px); }
}
 
/* ❌ 动画使用left/top(触发回流)*/
@keyframes slide {
    from { left: 0; }
    to { left: 100px; }
}

阻塞事件循环

长时间运行的同步代码会阻塞渲染和用户交互:

// ❌ 阻塞5秒
function processLargeArray(items) {
    items.forEach(item => heavyWork(item));
}
 
// ✅ 使用setTimeout分片
async function processLargeArray(items) {
    for (let i = 0; i < items.length; i++) {
        heavyWork(items[i]);
        if (i % 100 === 0) {
            await new Promise(resolve => setTimeout(resolve, 0));
        }
    }
}
 
// ✅ 使用Web Worker
const worker = new Worker('processor.js');

Chrome DevTools性能分析

Performance面板

  1. 记录页面操作
  2. 查找长任务(Long Task)
  3. 观察JS执行与渲染的交织

关键指标:

  • Task:JS执行单位
  • Layout:回流
  • Recalculate Style:样式重算
  • Paint:绘制

Memory面板

检测内存泄漏:

  1. 录制堆快照(Heap Snapshot)
  2. 对比操作前后的快照
  3. 查找 detached DOM树

Node.js事件循环

Node.js的事件循环分为6个阶段:

   ┌───────────────────────────┐
   │       Timers              │  ← setTimeout, setInterval
   ├───────────────────────────┤
   │   Pending Callbacks        │  ← I/O callbacks
   ├───────────────────────────┤
   │   Idle, Prepare            │  ← 内部使用
   ├───────────────────────────┤
   │       Poll                 │  ← 获取新I/O事件
   ├───────────────────────────┤
   │       Check                │  ← setImmediate
   ├───────────────────────────┤
   │    Close Callbacks         │  ← 关闭回调
   └───────────────────────────┘

关键区别

  • Node.js没有渲染管线(无头环境)
  • process.nextTick()在阶段之间、microtasks之前执行
  • setImmediate()在Check阶段执行

面试要点总结

“我理解浏览器为一个多阶段管道:解析HTML/CSS为树 → 合并为渲染树 → 计算布局 → 绘制像素 → 合成层。我通过避免布局抖动(读写批量化)、使用合成器友好属性(transform/opacity)进行动画、优化requestAnimationFrame来实现60fps。对于重计算,使用Web Workers保持主线程响应。”

核心理解

  1. JS执行 → 微任务 → 宏任务 → 渲染
  2. 渲染 Pipeline:DOM/CSSOM → Render Tree → Layout → Paint → Composite
  3. 优化策略:避免回流、使用transform、代码分片

参考资料

Footnotes

  1. How browsers work - Web.dev

  2. JavaScript Event Loop: The Complete Guide for 2026 - DevToolbox