草木一秋,飞火流萤一日蜉蝣 - 一个 Web 应用的生命周期(上)

722 阅读8分钟

浮生一日,蜉蝣一世

有话要说

一年一度的 1024 节到了,程序员们就像是一个个 1024,以最低调、踏实、核心的功能模块搭建起这个科技世界。

这一天,也希望大家有个愉快的周末,没有加班(滑稽)。

咳咳,言归正传。转眼就到了毕业季,也好久没有总结了,趁着找工作找实习的阶段,好好的学习一些新知识,复习旧知识,同时为博客仓库增加一砖一瓦。

本文是在一道经典面试题《从输入 URL 到页面呈现,这一过程到底发生了什么》的基础上扩展了一下下:《从启动浏览器到用户交互,这期间会发生什么》。

趁这道题我还没被面试过,先下手为强。


大图预警

Web 应用的生命周期
Web 应用的生命周期

图片在线

上图建议在大屏下浏览,因为太太太大了。同时也有在线文档与图片提供下载,画图工具为 processon


阶段性介绍

关于这个过程,我将它拆分为 14 个阶段,每个阶段都做相应的工作或者子任务,这样便于理解。

每个阶段分析的重点不一,可能有部分比较详细,有些则是一笔带过。若网友们有不同看法,欢迎讨论或纠正。

由于图片较大,一时间让人难以理解,请容我胡说八道一番:

  • 01 - 启动阶段

    • 启动 Chrome 浏览器,分配资源,以及其他操作
      • 分配浏览器进程
      • 分配网络进程
      • 分配渲染进程
      • 分配 GPU 进程
  • 02 - 用户交互阶段

    • 用户输入内容,有智能提示,用户决定后回车确认
    • 用户单击超链接
  • 03 - 浏览器进程处理交互信息阶段

    • 匹配是否符合 URL 规则
      • 补上协议,构建 HTTP 请求
    • 判断是否为搜索关键词
      • 使用默认搜索引擎搜索
    • 触发 onBeforeUnLoad 事件(如果有监听的话)
      • 可执行清理数据操作
      • 询问用户是否不保存当前页面离开
        • 确定离开,继续后续阶段
        • 取消离开,停止页面跳转,当前页不改变
    • Loading
      • 浏览器状态栏改变,标签页上的图标成 loading 状态
      • 加载中,等待新页面渲染,但旧页面依然存在
  • 04 - 检查本地缓存阶段(浏览器进程将请求(URL)发送到网络进程)

    • 先检查 Service Worker 缓存
    • 然后检查内存缓存
    • 再检查硬盘缓存
      • 强缓存
      • 协商缓存
    • 最后检查推送缓存
    • 若以上缓存命中,则使用缓存资源
    • 否则进行下一阶段
  • 05 - DNS 解析阶段

    • 若地址栏为 IP 地址,则进行下一阶段
    • 否则为域名,进行 DNS 解析
      • 查看本地 Hosts 文件是否有地址缓存
        • 有则进行下一阶段
        • 否则进行 DNS 查询
          • 递归查询
          • 迭代查询
        • 将查询结果缓存在本地,同时进入下一阶段
  • 06 - HTTP 【请求/响应】处理阶段

    • 建立 TCP 连接
      • 三次握手
    • 构建 HTTP 请求
    • 响应数据
    • 解析响应数据
      • 响应头中是否包含 Connection:Keep-Alive 字段
        • 包含,保活
        • 不包含,四次挥手断开连接
      • 是否重定向
        • 回到第 04 阶段重复流程
      • 是否为下载资源类型
        • 使用浏览器下载管理器下载资源
      • 是否为 HTML 类型(Content-Type:text/html)
        • 进行下一阶段
  • 07 - 文档提交阶段

    • 网络进程将响应头数据发送给浏览器进程
    • 浏览器进程向渲染进程发送【提交文档】指令
    • 渲染进程接收指令后,与网络进程搭建数据传输管道
    • 数据传输完成,渲染进程向浏览器进程发送【确认提交】指令
    • 浏览器进程更新浏览器界面状态
      • 标签页上的图标停止转动
  • 08 - DOM Tree 构建阶段

    • 预解析(检测是否有静态资源要下载)
      • 是否为关键资源
        • 立即下载
      • 非关键资源
        • 需要时再下载
    • 有容错机制,会自动修复错误
      • 例如在 head 标签内放置 p 标签,这个 p 标签最终会移动到 body 标签内
    • 解析到 script 标签
      • 呼叫 JavaScript 引擎执行脚本代码
      • 暂停 DOM Tree 的构建
      • JavaScript 代码执行完毕继续 DOM Tree 的构建
  • 09 - 样式重计算阶段(Recalculate Style) -【重排(回流)】

    • 解析 CSS 代码,生成 CSSOM (StyleSheets)
    • 将 CSSOM 属性值标准化
    • 计算每个元素的样式
    • 改变元素的几何尺寸或位置会触发重排,即执行该阶段以及后续流程
  • 10 - 布局树构建阶段(Layout Tree)

    • 创建可见元素布局树
      • 例如页面上只可看到 body 标签中的内容
      • 忽略隐藏的内容(display:none)
    • 布局计算
  • 11 - 图层树构建阶段(Layer Tree)

    • 分层构建(如图 PS 图层)
  • 12 - 绘制阶段(Paint) - 【重绘】

    • 生成绘制指令列表提交到合成线程
    • 改变元素的样式将触发重绘,即执行该阶段以及后续流程
  • 13 - 合成阶段 - 【合成】

    • 图层分块
    • 栅格化
      • 借助 GPU 快速栅格化
      • 生成位图数据
      • 保存于 GPU 内存
    • 所有图块栅格化完毕,发送【合成绘制】指令到浏览器进程的 Viz 组件
    • 元素应用动画会触发合成,即执行该阶段以及后续流程
  • 14 - 展示阶段(Display)

    • 浏览器进程 Viz 组件接收指令
    • 读取 GPU 内存中的位图数据进行合成绘制
    • 将绘制后的内容提交到操作系统内存
    • 将操作系统内存中的数据提交到显卡缓存区
    • 页面展示

其实还不算完,以上为页面构建阶段,后续还有事件处理阶段,该阶段将涉及事件循环机制,由于涉及内容较多,有机会再学习总结。

以上所叙述的内容一起组成了一个 Web 应用的生命周期。


能做什么

大概了解了这么一个过程,那么我们对于网站就可以有了一些优化方案,提升用户体验:

  • 资源缓存
  • 资源压缩、内联
  • 减少 HTTP 请求
  • 合理的代码规范
  • ......

总结与收获

纵观本次研究的从启动浏览器到页面呈现的这个过程,其中涉及【操作系统】、【计算机网络】、【浏览器原理】等等很多内容,例如:

  • 操作系统
    • 进程与线程
  • 计算机网络
    • TCP 协议
    • DNS 协议
    • HTTP 协议
  • 浏览器
    • DOM 解析
    • CSS 解析
    • JavaScript 解释执行
    • 布局与图层
    • 事件处理
    • 缓存
  • ......

当然还有一些图中没有表现出来的概念,比如操作系统调度算法,图形方面作等等。

每一个技术点都能单独做一篇长文来介绍,网络上也有很多好文,大家也可以搜索相关的知识进行学习。

总的来说,这一次的学习让我受益匪浅。因为对于我来说,每一个知识点都值得去考虑。至于挖的多深,取决于你对这个知识求知欲望以及对于你当前的工作是否有益。


人生短短几个秋

蜉蝣朝生暮死,站在我们的角度去看待它,一天是短暂的。可是对于蜉蝣来说,这一天也许是很漫长的,就像我们生活一年一样。

同理,从启动浏览器到打开某个网站,对于我们来说,网络通畅情况下,基本上是秒开了。

但从在浏览器内部的角度看,如以上的分析,经历了这么多(实际上肯定不止这么多)的过程,却要在秒级乃至毫秒级别之间完成。

能实现这样的效果,少不了前人呕心沥血的付出。

向默默付出的前辈们致敬!

《大鱼海棠》:人生是一场旅程。我们经历了几次轮回,才换来这个旅程。而这个旅程很短,因此不妨大胆一些,不妨大胆一些去爱一个人,去攀一座山,去追一个梦。


【芝诺】:人的知识就好比一个圆圈,圆圈里面是已知的,圆圈外面是未知的。你知道的越多,圆圈也就越大,你不知道的也就越多。


参考文献(排名不分先后)

本文使用 mdnice 排版