浮生一日,蜉蝣一世
有话要说
一年一度的 1024 节到了,程序员们就像是一个个 1024,以最低调、踏实、核心的功能模块搭建起这个科技世界。
这一天,也希望大家有个愉快的周末,没有加班(滑稽)。
咳咳,言归正传。转眼就到了毕业季,也好久没有总结了,趁着找工作找实习的阶段,好好的学习一些新知识,复习旧知识,同时为博客仓库增加一砖一瓦。
本文是在一道经典面试题《从输入 URL 到页面呈现,这一过程到底发生了什么》的基础上扩展了一下下:《从启动浏览器到用户交互,这期间会发生什么》。
趁这道题我还没被面试过,先下手为强。
大图预警
图片在线
上图建议在大屏下浏览,因为太太太大了。同时也有在线文档与图片提供下载,画图工具为 processon
阶段性介绍
关于这个过程,我将它拆分为 14 个阶段,每个阶段都做相应的工作或者子任务,这样便于理解。
每个阶段分析的重点不一,可能有部分比较详细,有些则是一笔带过。若网友们有不同看法,欢迎讨论或纠正。
由于图片较大,一时间让人难以理解,请容我胡说八道一番:
01 - 启动阶段
- 启动 Chrome 浏览器,分配资源,以及其他操作
- 分配浏览器进程
- 分配网络进程
- 分配渲染进程
- 分配 GPU 进程
- 启动 Chrome 浏览器,分配资源,以及其他操作
02 - 用户交互阶段
- 用户输入内容,有智能提示,用户决定后回车确认
- 用户单击超链接
03 - 浏览器进程处理交互信息阶段
- 匹配是否符合 URL 规则
- 补上协议,构建 HTTP 请求
- 判断是否为搜索关键词
- 使用默认搜索引擎搜索
- 触发 onBeforeUnLoad 事件(如果有监听的话)
- 可执行清理数据操作
- 询问用户是否不保存当前页面离开
- 确定离开,继续后续阶段
- 取消离开,停止页面跳转,当前页不改变
- Loading
- 浏览器状态栏改变,标签页上的图标成 loading 状态
- 加载中,等待新页面渲染,但旧页面依然存在
- 匹配是否符合 URL 规则
04 - 检查本地缓存阶段(浏览器进程将请求(URL)发送到网络进程)
- 先检查 Service Worker 缓存
- 然后检查内存缓存
- 再检查硬盘缓存
- 强缓存
- 协商缓存
- 最后检查推送缓存
- 若以上缓存命中,则使用缓存资源
- 否则进行下一阶段
05 - DNS 解析阶段
- 若地址栏为 IP 地址,则进行下一阶段
- 否则为域名,进行 DNS 解析
- 查看本地 Hosts 文件是否有地址缓存
- 有则进行下一阶段
- 否则进行 DNS 查询
- 递归查询
- 迭代查询
- 将查询结果缓存在本地,同时进入下一阶段
- 查看本地 Hosts 文件是否有地址缓存
06 - HTTP 【请求/响应】处理阶段
- 建立 TCP 连接
- 三次握手
- 构建 HTTP 请求
- 响应数据
- 解析响应数据
- 响应头中是否包含 Connection:Keep-Alive 字段
- 包含,保活
- 不包含,四次挥手断开连接
- 是否重定向
- 回到第 04 阶段重复流程
- 是否为下载资源类型
- 使用浏览器下载管理器下载资源
- 是否为 HTML 类型(Content-Type:text/html)
- 进行下一阶段
- 响应头中是否包含 Connection:Keep-Alive 字段
- 建立 TCP 连接
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 解释执行
- 布局与图层
- 事件处理
- 缓存
- ......
当然还有一些图中没有表现出来的概念,比如操作系统调度算法,图形方面作等等。
每一个技术点都能单独做一篇长文来介绍,网络上也有很多好文,大家也可以搜索相关的知识进行学习。
总的来说,这一次的学习让我受益匪浅。因为对于我来说,每一个知识点都值得去考虑。至于挖的多深,取决于你对这个知识求知欲望以及对于你当前的工作是否有益。
人生短短几个秋
蜉蝣朝生暮死,站在我们的角度去看待它,一天是短暂的。可是对于蜉蝣来说,这一天也许是很漫长的,就像我们生活一年一样。
同理,从启动浏览器到打开某个网站,对于我们来说,网络通畅情况下,基本上是秒开了。
但从在浏览器内部的角度看,如以上的分析,经历了这么多(实际上肯定不止这么多)的过程,却要在秒级乃至毫秒级别之间完成。
能实现这样的效果,少不了前人呕心沥血的付出。
向默默付出的前辈们致敬!
《大鱼海棠》:人生是一场旅程。我们经历了几次轮回,才换来这个旅程。而这个旅程很短,因此不妨大胆一些,不妨大胆一些去爱一个人,去攀一座山,去追一个梦。
【芝诺】:人的知识就好比一个圆圈,圆圈里面是已知的,圆圈外面是未知的。你知道的越多,圆圈也就越大,你不知道的也就越多。
参考文献(排名不分先后)
- 陈徵 - 浮生一日,蜉蝣一世
- JavaScript 忍者秘籍(第 2 版)
- 李兵 - 浏览器工作原理与实践
- 小蘑菇小哥 - 一文读懂前端缓存
- 贝程学院小王子 - 浏览器的沙箱机制
- zyy1688 - 浏览器进程相关知识点
- 神三元 - 浏览器灵魂之问,请问你能接得住几个
- 咸鱼老弟 - 从输入 URL 到页面展示到底发生了什么
本文使用 mdnice 排版