Skip to content

渲染周期

本文详细介绍网页从用户输入URL到页面完全加载、交互、最终关闭的整个生命周期过程,深入剖析浏览器是如何工作的。

页面生命周期概览

一个网页的完整生命周期包括以下几个阶段:

输入URL → 导航 → 请求/响应 → 解析 → 渲染 → 交互 → 关闭

导航阶段

1. 用户输入处理

当用户在地址栏输入URL时:

  1. 输入分析

    • 浏览器解析输入内容
    • 判断是URL、搜索关键词还是其他类型
    • 对于不完整URL,添加默认协议(如http://)
  2. 地址栏建议

    • 根据历史记录、书签和预设搜索引擎提供建议
    • 可能进行DNS预取或预连接以优化响应速度
  3. URL解析

    • 分解URL为组件(协议、域名、路径、查询参数等)
    • 执行URL编码/解码
    • 检查和应用同源策略

2. 导航决策

一旦确定了目标URL:

  1. 检查缓存

    • 查找上一次访问记录
    • 检查导航缓存
    • 确定是前进/后退导航还是新导航
  2. 页面卸载准备

    • 当前页面触发beforeunload事件
    • 用户可能需要确认离开(如有未保存的表单数据)
    • 当前页面可能发送信标(beacon)请求
  3. 进程/线程准备

    • 决定是否复用现有渲染进程
    • 或创建新的渲染进程
    • 分配必要的系统资源

网络请求阶段

1. DNS解析

将域名转换为IP地址:

  1. 缓存查询

    • 检查浏览器DNS缓存
    • 检查操作系统DNS缓存
    • 检查路由器DNS缓存
  2. 递归查询

    • 联系本地DNS服务器
    • 如果需要,执行完整DNS解析过程
    • 获取目标服务器IP地址

2. 连接建立

通过网络连接到服务器:

  1. TCP连接

    • 执行TCP三次握手
    • 建立可靠的连接
    • 协商连接参数
  2. TLS/SSL握手 (对于HTTPS)

    • 协商加密参数
    • 验证服务器证书
    • 建立加密通道

3. HTTP交互

发送请求并接收响应:

  1. 发送HTTP请求

    • 构建HTTP请求头(包含User-Agent、Accept、Cookie等)
    • 添加请求体(如适用)
    • 通过网络发送请求
  2. 服务器处理

    • 服务器接收并处理请求
    • 执行必要的逻辑(路由、认证等)
    • 生成响应
  3. 接收HTTP响应

    • 接收状态行和响应头
    • 接收响应体
    • 处理可能的重定向(3xx状态码)

解析阶段

1. HTML解析

将HTML文本转换为DOM树:

  1. 字节流解码

    • 根据指定编码(如UTF-8)将字节转换为字符
    • 处理字符编码问题
  2. 标记化(Tokenization)

    • 将字符流分解为标记(tokens)
    • 识别开始标签、结束标签、属性等
  3. 构建DOM树

    • 基于标记创建DOM节点
    • 建立节点之间的层次关系
    • 处理嵌套结构
javascript
// DOM树的简化表示
{
  nodeType: 9, // Document
  children: [
    {
      nodeType: 1, // Element
      tagName: 'html',
      children: [
        {
          nodeType: 1,
          tagName: 'head',
          // ...
        },
        {
          nodeType: 1,
          tagName: 'body',
          // ...
        }
      ]
    }
  ]
}

2. 子资源加载

处理HTML中引用的资源:

  1. 预加载扫描

    • 快速扫描HTML以发现关键资源(CSS、JavaScript、字体等)
    • 尽早启动资源请求
  2. 资源优先级划分

    • CSS和阻塞渲染的JavaScript获得高优先级
    • 图片和非关键资源获得低优先级
  3. 资源加载与处理

    • 并行请求多个资源(受HTTP协议和浏览器限制)
    • 处理不同类型的资源(解析CSS、编译JavaScript等)

3. JavaScript执行

解析和执行脚本:

  1. 解析

    • 将JavaScript源码解析为抽象语法树(AST)
    • 检查语法错误
  2. 编译

    • 将AST转换为字节码或机器码
    • 应用优化(JIT编译、内联等)
  3. 执行

    • 运行代码
    • 可能修改DOM或CSSOM
    • 可能触发额外的网络请求

4. CSS处理

解析样式信息:

  1. CSS解析

    • 解析CSS规则
    • 处理@import、媒体查询等
  2. 构建CSSOM

    • 创建CSS对象模型
    • 解析选择器和属性
    • 计算级联和继承
javascript
// CSSOM树的简化表示
{
  rules: [
    {
      selectorText: 'body',
      style: {
        color: 'black',
        fontSize: '16px'
      }
    },
    {
      selectorText: '.header',
      style: {
        backgroundColor: 'blue'
      }
    }
  ]
}

渲染阶段

1. 渲染树构建

将DOM和CSSOM组合:

  1. 合并DOM和CSSOM

    • 遍历DOM树
    • 应用匹配的样式规则
    • 考虑继承和层叠
  2. 过滤不可见元素

    • 排除不渲染的元素(如display:none、script、meta)
    • 考虑媒体查询
  3. 构建渲染树

    • 包含所有可见内容及其计算样式
    • 准备进行布局

2. 布局(Layout)

计算元素的精确位置和大小:

  1. 初始布局

    • 计算视口大小
    • 确定元素的尺寸和位置
    • 处理盒模型、浮动、定位等
  2. 布局计算

    • 自上而下流式布局
    • 处理相对和绝对定位
    • 计算盒子的精确几何信息
  3. 布局树生成

    • 创建包含位置和尺寸信息的布局树
    • 为绘制阶段做准备

3. 绘制(Paint)

将布局转换为屏幕上的像素:

  1. 绘制顺序确定

    • 创建绘制记录
    • 确定绘制顺序(z-index层叠)
  2. 分层(Layer)

    • 将内容分为多个图层
    • 识别需要单独合成的部分(如具有transform、opacity的元素)
  3. 光栅化(Rasterization)

    • 将矢量信息转换为位图(像素)
    • 可能使用GPU加速

4. 合成(Compositing)

将各个层组合成最终画面:

  1. 图层合成

    • 将所有层按正确的顺序合并
    • 应用变换和效果
  2. 显示合成结果

    • 将最终图像发送到显示器
    • 处理高刷新率和动画

交互阶段

1. 初始交互响应

页面首次可交互:

  1. 关键渲染路径完成

    • 首次内容绘制(FCP)
    • 首次有意义绘制(FMP)
    • 可交互时间(TTI)
  2. 事件监听器激活

    • JavaScript事件监听器开始响应
    • 用户可以与页面元素交互

2. 用户交互处理

处理用户输入:

  1. 事件捕获与冒泡

    • 事件从根节点传播到目标
    • 然后从目标冒泡回根节点
  2. 事件处理

    • 执行关联的事件处理函数
    • 可能修改DOM
    • 可能触发重新渲染

3. 渲染更新

响应DOM变化:

  1. 增量布局

    • 计算DOM变化的影响
    • 尽量只重新布局受影响的部分
  2. 重绘

    • 更新受影响区域的像素
    • 避免全页面重绘
  3. 合成更新

    • 只更新变化的图层
    • 优化性能
javascript
// 高效的DOM操作示例
// 1. 批量更新
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
  const el = document.createElement('div');
  el.textContent = `Item ${i}`;
  fragment.appendChild(el);
}
document.getElementById('container').appendChild(fragment);

// 2. 避免强制同步布局
requestAnimationFrame(() => {
  const width = element.offsetWidth; // 读取
  elements.forEach(el => {
    el.style.width = width + 'px'; // 写入
  });
});

页面生命周期事件

浏览器提供了一系列事件来跟踪页面生命周期:

  1. 导航事件

    • DOMContentLoaded:DOM完全加载和解析
    • load:页面及所有资源加载完成
    • beforeunload:用户即将离开页面
    • unload:用户离开页面
  2. 可见性事件

    • visibilitychange:页面可见性变化
    • pageshow:页面显示
    • pagehide:页面隐藏
  3. 资源事件

    • readystatechange:document加载状态变化
    • loadstartprogressloadend等:资源加载过程
javascript
// 生命周期事件监听示例
document.addEventListener('DOMContentLoaded', () => {
  console.log('DOM已加载,可以操作DOM元素');
});

window.addEventListener('load', () => {
  console.log('页面完全加载,包括所有依赖资源');
});

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    console.log('页面不可见,暂停非必要操作');
  } else {
    console.log('页面可见,恢复操作');
  }
});

window.addEventListener('beforeunload', (event) => {
  // 提示用户确认离开
  event.preventDefault();
  event.returnValue = '';
});

页面关闭阶段

1. 触发关闭

页面关闭可由多种原因引起:

  1. 用户操作

    • 关闭标签页/窗口
    • 导航到其他页面
    • 刷新页面
  2. 程序性关闭

    • JavaScript调用window.close()
    • 页面重定向
  3. 浏览器/系统行为

    • 浏览器关闭
    • 系统关机

2. 资源清理

浏览器执行清理工作:

  1. 事件处理

    • 触发beforeunload事件
    • 触发unload事件
    • 执行已注册的清理函数
  2. 状态保存

    • 保存会话历史
    • 保存滚动位置(用于前进/后退导航)
    • 可能发送信标请求(统计数据)
  3. 资源释放

    • 取消待处理的网络请求
    • 释放内存
    • 终止后台线程和Service Workers(根据需要)

3. 进程清理

根据浏览器架构执行最终清理:

  1. 渲染进程处理

    • 终止所有JavaScript执行
    • 释放图形和内存资源
  2. 浏览器进程处理

    • 更新历史记录和UI
    • 释放相关系统资源

性能优化关键点

针对页面生命周期的各个阶段优化:

1. 导航和请求阶段

  • 使用DNS预解析和预连接
  • 实施有效的缓存策略
  • 利用CDN减少服务器响应时间

2. 解析和渲染阶段

  • 最小化关键渲染路径
  • 延迟加载非关键资源
  • 优化JavaScript执行
  • 避免渲染阻塞

3. 交互阶段

  • 实现响应式设计
  • 优化事件处理
  • 使用防抖和节流
  • 异步处理长任务

4. 关闭阶段

  • 优雅处理页面卸载
  • 保存关键用户状态
  • 避免阻塞beforeunload事件

诊断与监控工具

用于分析页面生命周期的工具:

  1. 浏览器开发者工具

    • Performance面板:分析渲染性能
    • Network面板:监控资源加载
    • Memory面板:跟踪内存使用
  2. 性能API

    • Navigation Timing API:测量导航和加载性能
    • Resource Timing API:测量资源加载时间
    • Performance Observer:监控性能事件
  3. Web Vitals指标

    • LCP(Largest Contentful Paint):最大内容绘制
    • FID(First Input Delay):首次输入延迟
    • CLS(Cumulative Layout Shift):累积布局偏移
javascript
// 使用Performance API测量页面加载性能
window.addEventListener('load', () => {
  const perfData = window.performance.timing;
  const pageLoadTime = perfData.loadEventEnd - perfData.navigationStart;
  console.log(`页面加载时间: ${pageLoadTime}ms`);

  const domReadyTime = perfData.domComplete - perfData.domLoading;
  console.log(`DOM处理时间: ${domReadyTime}ms`);
});