Skip to content

浏览器原理

一、浏览器渲染原理

1. 浏览器渲染流程

主要步骤:

  1. 解析 HTML:构建 DOM 树
  2. 解析 CSS:构建 CSSOM 树
  3. 合并:将 DOM 和 CSSOM 合并成渲染树(Render Tree)
  4. 布局(Layout/Reflow):计算元素的位置和大小
  5. 绘制(Paint):填充像素
  6. 合成(Composite):将图层合并

2. DOM 树构建

HTML 解析过程:

  • 字节流 → 字符流 → Token → Node → DOM 树
  • 浏览器使用状态机解析 HTML
  • 遇到 script 标签会阻塞解析,执行 JavaScript

3. CSSOM 树构建

CSS 解析过程:

  • 字节流 → 字符流 → Token → Node → CSSOM 树
  • CSS 解析不会阻塞 DOM 解析,但会阻塞渲染
  • CSS 规则从右到左匹配(提高匹配效率)

4. 渲染树(Render Tree)

渲染树的特点:

  • 只包含需要渲染的元素(display: none 不包含)
  • 每个节点都有对应的 CSS 样式
  • 只包含可见元素

5. 布局(Layout/Reflow)

布局过程:

  • 计算元素的位置和大小
  • 从根节点开始递归计算
  • 使用盒模型计算

触发重排(Reflow)的操作:

  • 修改 DOM 结构
  • 修改元素样式(width、height、position 等)
  • 窗口大小变化
  • 字体大小变化

6. 绘制(Paint)

绘制过程:

  • 填充像素
  • 绘制文本、颜色、图片等
  • 在多个图层上绘制

触发重绘(Repaint)的操作:

  • 修改颜色、背景色
  • 修改边框样式
  • 不影响布局的样式变化

7. 合成(Composite)

合成过程:

  • 将多个图层合并
  • 使用 GPU 加速
  • 避免整个页面重排和重绘

二、重排和重绘

1. 重排(Reflow)

什么是重排? 当 DOM 结构或样式变化,导致浏览器重新计算元素位置和大小。

触发重排的操作:

javascript
// 修改 DOM 结构
element.appendChild(newElement);

// 修改元素样式
element.style.width = '100px';
element.style.height = '100px';
element.style.position = 'absolute';

// 获取布局信息(强制重排)
const width = element.offsetWidth;
const height = element.offsetHeight;

优化重排:

  • 批量修改 DOM(使用 DocumentFragment)
  • 避免频繁读取布局属性
  • 使用 transform 代替修改位置属性
  • 使用 visibility 代替 display: none
javascript
// 不推荐:多次重排
element.style.left = '10px';
element.style.top = '10px';
element.style.width = '100px';

// 推荐:一次重排
element.style.cssText = 'left: 10px; top: 10px; width: 100px;';

// 或者使用 class
element.className = 'new-style';

2. 重绘(Repaint)

什么是重绘? 当元素样式变化,但不影响布局,浏览器只需要重新绘制。

触发重绘的操作:

javascript
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.border = '1px solid black';

优化重绘:

  • 减少重绘次数
  • 使用 CSS3 动画(GPU 加速)
  • 使用 will-change 提示浏览器优化

三、事件循环(Event Loop)

1. JavaScript 执行机制

单线程:

  • JavaScript 是单线程的,同一时间只能执行一个任务
  • 通过事件循环机制处理异步任务

执行栈:

  • 用于存储同步代码的执行上下文
  • 先进后出(LIFO)

任务队列:

  • 宏任务队列(MacroTask):setTimeout、setInterval、I/O、UI 渲染
  • 微任务队列(MicroTask):Promise.then、queueMicrotask、MutationObserver

2. 事件循环流程

执行顺序:

  1. 执行同步代码
  2. 执行微任务队列(全部执行完)
  3. 执行一个宏任务
  4. 执行微任务队列(全部执行完)
  5. 重复步骤 3-4
javascript
console.log(1);

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

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

console.log(4);

// 输出:1, 4, 3, 2

执行过程:

  1. 执行 console.log(1) → 输出 1
  2. 遇到 setTimeout,加入宏任务队列
  3. 遇到 Promise.resolve().then(),加入微任务队列
  4. 执行 console.log(4) → 输出 4
  5. 同步代码执行完毕,执行微任务队列 → 输出 3
  6. 执行宏任务队列 → 输出 2

3. 微任务和宏任务

微任务(MicroTask):

  • Promise.then()
  • Promise.catch()
  • Promise.finally()
  • queueMicrotask()
  • MutationObserver

宏任务(MacroTask):

  • setTimeout
  • setInterval
  • setImmediate(Node.js)
  • I/O
  • UI 渲染
  • requestAnimationFrame
javascript
console.log(1);

setTimeout(() => {
  console.log(2);
  Promise.resolve().then(() => {
    console.log(3);
  });
}, 0);

Promise.resolve().then(() => {
  console.log(4);
  setTimeout(() => {
    console.log(5);
  }, 0);
});

console.log(6);

// 输出:1, 6, 4, 2, 3, 5

四、内存管理

1. 内存生命周期

分配内存:

  • 声明变量、函数、对象时分配内存

使用内存:

  • 读写变量、调用函数

释放内存:

  • 垃圾回收机制自动释放

2. 垃圾回收

标记清除(Mark and Sweep):

  • 标记所有从根节点可达的对象
  • 清除未标记的对象(垃圾)

引用计数:

  • 记录每个对象被引用的次数
  • 当引用次数为 0 时回收
  • 无法处理循环引用

3. 内存泄漏

常见内存泄漏:

1. 全局变量

javascript
// 泄漏
function leak() {
  window.data = new Array(1000000);
}

// 解决:使用局部变量或及时清理

2. 闭包

javascript
// 泄漏
function outer() {
  const data = new Array(1000000);
  return function inner() {
    console.log('inner');
  };
}

// 解决:避免不必要的闭包

3. DOM 引用

javascript
// 泄漏
const element = document.getElementById('myElement');
const data = { element };

// 即使删除 DOM,data.element 仍引用它
element.remove();

// 解决:删除 DOM 前清理引用
data.element = null;

4. 定时器未清理

javascript
// 泄漏
const timer = setInterval(() => {
  console.log('running');
}, 1000);

// 解决:及时清理
clearInterval(timer);

5. 事件监听器未移除

javascript
// 泄漏
element.addEventListener('click', handleClick);

// 解决:移除事件监听器
element.removeEventListener('click', handleClick);

五、性能优化

1. 资源加载优化

图片优化:

  • 使用适当的图片格式(WebP、AVIF)
  • 图片懒加载
  • 响应式图片(srcset)
  • 压缩图片

脚本优化:

  • 使用 defer 或 async
  • 代码分割(Code Splitting)
  • Tree Shaking
  • 压缩和混淆
html
<!-- defer:DOM 解析完成后执行 -->
<script src="app.js" defer></script>

<!-- async:下载完成后立即执行 -->
<script src="analytics.js" async></script>

2. 渲染优化

CSS 优化:

  • 避免使用 @import
  • 内联关键 CSS
  • 避免深层嵌套选择器
  • 使用 transform 和 opacity(GPU 加速)

JavaScript 优化:

  • 避免阻塞渲染
  • 使用 requestIdleCallback 处理非关键任务
  • 使用 requestAnimationFrame 优化动画
javascript
// 使用 requestAnimationFrame 优化动画
function animate() {
  // 动画逻辑
  requestAnimationFrame(animate);
}
animate();

3. 缓存策略

浏览器缓存:

  • 强缓存:Cache-Control、Expires
  • 协商缓存:ETag、Last-Modified

HTTP 缓存头:

http
Cache-Control: max-age=3600
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT

4. 网络优化

CDN:

  • 使用 CDN 加速资源加载
  • 减少延迟

HTTP/2:

  • 多路复用
  • 服务器推送
  • 头部压缩

预加载和预渲染:

html
<!-- 预加载资源 -->
<link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

<!-- 预渲染页面 -->
<link rel="prerender" href="https://example.com/page">

六、安全

1. XSS(跨站脚本攻击)

XSS 类型:

  • 存储型 XSS:恶意脚本存储在服务器
  • 反射型 XSS:恶意脚本在 URL 中
  • DOM 型 XSS:客户端脚本修改 DOM

防护措施:

  • 输入验证和过滤
  • 输出转义
  • 使用 Content-Security-Policy(CSP)
  • 使用 HttpOnly Cookie

2. CSRF(跨站请求伪造)

防护措施:

  • 使用 CSRF Token
  • 验证 Referer
  • 使用 SameSite Cookie
javascript
// CSRF Token
const token = document.querySelector('meta[name="csrf-token"]').content;
fetch('/api/data', {
  method: 'POST',
  headers: {
    'X-CSRF-Token': token
  }
});

3. 其他安全问题

点击劫持:

  • 使用 X-Frame-Options

中间人攻击:

  • 使用 HTTPS

敏感信息泄露:

  • 不要在客户端存储敏感信息
  • 使用 HTTPS 传输数据

七、常见面试题

1. 浏览器渲染过程?

  1. 解析 HTML 构建 DOM 树
  2. 解析 CSS 构建 CSSOM 树
  3. 合并成渲染树
  4. 布局(计算位置和大小)
  5. 绘制(填充像素)
  6. 合成(合并图层)

2. 重排和重绘的区别?

  • 重排:重新计算元素位置和大小(更耗性能)
  • 重绘:重新绘制元素样式(相对较轻)

重排一定会触发重绘,但重绘不一定触发重排。

3. 如何优化页面性能?

  • 资源加载优化:压缩、懒加载、CDN
  • 渲染优化:减少重排重绘、使用 GPU 加速
  • JavaScript 优化:代码分割、异步加载
  • 缓存策略:合理使用浏览器缓存
  • 网络优化:使用 HTTP/2、预加载

4. 事件循环机制?

JavaScript 是单线程的,通过事件循环处理异步任务。执行顺序:同步代码 → 微任务 → 宏任务 → 微任务 → ...

5. 内存泄漏如何排查?

  • 使用 Chrome DevTools 的 Memory 面板
  • 使用 Performance 面板分析内存使用
  • 检查全局变量、闭包、DOM 引用、定时器等

基于 VitePress 构建 | Copyright © 2026-present