在Chrome浏览器中,我们点击右上角三个点–更多工具–任务管理器。 会看到打开一个页面,启动了多个进程。 在进行每个进程分析前,先明确下线程与进程之间的关系。
单进程浏览器是指所有功能模块(网路、插件、JS运行环境、渲染引擎等)都是运行在同一个进程里,2007年以前,市面上浏览器都是单进程架构。 这种结构模式:不稳定、不流畅、不安全。
2016年,Chrome官方团队使用“面向服务的架构”(SOA)的思想设计了新的Chrome架构。
衡量web性能的一个重要指标叫"FP(First Paint)",指页面加载到首次开始绘制的时长。影响FP的重要因素有一个网络加载速度。 网络加载的过程就是数据包的传输过程,站在数据包角度,从“数据包如何送达主机”,“主机如何将数据包转交给应用”,“数据如何被完整送达应用程序”三个方面来分析。
IP是非常底层的协议,只负责把数据包传达给目标主机,但是并不知道交给哪个应用程序。 因此需要基于IP之上开发能和应用打交道的协议,最常见的协议就是UDP协议–User Datagram Protocol(用户数据包协议) UDP通过端口号把数据包分发给正确的程序。 UDP可以校验数据是否正确,但是不提供重传机制,只是丢弃当前的包。虽然UDP不能保证数据的准确性,但是传输速度非常快。
TCP(Transition Control Protocol,传输控制协议),是一种面向连接的、可靠的、基于字节流的传输层通信协议。 TCP提供重传机制、引入了数据包排序机制。
一个完整的TCP连接的生命周期包含了“建立连接“、”传输数据“、“断开连接”三个阶段。 建立连接阶段:这个阶段通过三次握手来建立客户端与服务端之间的连接,它们之间总共要发送三个数据包以确认连接的建立。 传输数据阶段:该阶段,接收端需要对每个数据包进行确认操作。 断开连接阶段:四次挥手来保证双方都能断开连接。
HTTP协议是建立在TCP连接基础之上的。 HTTP是一种允许浏览器向服务器获取资源的协议,是Web的基础。 浏览器端发起HTTP请求流程如下:
浏览器构建请求行信息,构建好后,浏览器准备发起网络请求。
在准备发起网路请求阶段,浏览器偷偷的在它的缓存中查询是否有要请求的资源。 若有:拦截请求,返回资源副本,直接结束请求。 若缓存查找失败:继续下一步。
浏览器第一步会请求域名系统DNS返回域名对应的IP,如果没有特别指明端口号,则默认为80。 (浏览器提供了DNS数据缓存服务,若缓存过也就不会去请求,直接解析。从而减少一次网络请求)
拿到IP地址与端口号后,还需要在TCP队列中排队才能建立TCP连接。 这是因为:Chrome有个机制,同一个域名同时最多只能建立6个TCP连接,若此刻同时有10个请求发生。则四个会进入TCP队列进行排队。 当然,若当前请求数量少于6个,则会直接进入下一步。
建立TCP连接,上一节我们已经知道,一个完整的TCP连接过程包括“建立连接”、“数据传输”、“断开连接“三个阶段。
HTTP请求是在TCP连接的数据传输阶段工作的,这个时候浏览器向服务器发送请求行,它包括请求方法、请求URI、HTTP版本协议。HTTP中的数据在这个通信过程中传输。
返回请求:通过 ‘curl -i domainName’命令,可以返回响应行(协议版本和状态码)、响应头(服务端返回请求的时间、Cookie、返回的数据类型等)、响应体信息. 通常情况,一旦服务器向客户端返回了请求数据,它就要关闭TCP连接,但是如果浏览器或者服务器设置了Connection:keep-alive,那么TCP连接在发送后将仍保持打开状态。保持TCP连接可以省去下次请求时需要建立连接的时间,提升资源加载速度。
主要原因肯定是第一次加载页面的过程中,缓存了一些数据(从上面的过程分析,我们知道DNS缓存和页面资源缓存这两块数据是会被浏览器缓存起来的). 网站把很多资源都缓存到了本地,浏览器缓存直接使用本地副本来回应请求,而不会产生真实的网络请求,从而节省了时间。
简单地说,如果服务器端发送的响应头内有 Set-Cookie 的字段,那么浏览器就会将该字段的内容保持到本地。当下次客户端再往该服务器发送请求时,客户端会自动在请求头中加入 Cookie 值后再发送出去。服务器端发现客户端发送过来的 Cookie 后,会去检查究竟是从哪一个客户端发来的连接请求,然后对比服务器上的记录,最后得到该用户的状态信息。
从输入URL到页面展示
如果是搜索内容,地址栏使用浏览器默认的搜索引擎合成带关键字的URL,如果符合URL规则,则合成完整的URL。
浏览器主进程通过进程间通信(IPC)把URL请求发送到网络进程,网络进程接收到URL请求后,会发起真正的URL请求。 首先,浏览器会查找本地缓存是否缓存了该资源,如果缓存则直接返回资源给浏览器主进程,如果没有缓存,那么直接进入网络请求流程:请求前的第一步是DNS解析,用来获取域名对应的IP地址,如果是HTTPS协议,还需要建立TLS连接。 接下来就是利用IP地址和服务器建立TCP连接,连接建立后,浏览器端会构建请求行、请求头等信息。 服务器接收到请求信息后,根据请求信息生成响应行、响应头发给网络进程。网络进程接收到后,开始解析响应数据。
同一站点:协议相同、根域名相同。 通常情况下,打开一个新的页面,浏览器就会为开辟一个新的渲染进程,但若从A页面打开的B页面,它们同属同一站点,那么使用同一渲染进程。
浏览器进程将网络进程接受到的HTML数据提交给渲染进程。
一旦文档被提交,渲染进程就开始页面解析和子资源加载了。
按照渲染的时间顺序,流水线可以分为:构建DOM树、样式计算、布局计算、分层、绘制、分块、光栅化、合成。 每个阶段需要关注:输入的内容、处理过程、输出的内容
因为浏览器无法直接理解和使用HTML,所以需要将HTML转换为浏览器能够理解的结构–DOM树。
样式计算的目的是为了计算出DOM节点中每个元素的具体样式
DOM结构有了、样式结构有了,需要对几何位置信息进行布局。
页面开始绘制之前,浏览器会对3D变换、页面滚动、z-index等操作去为特定的节点生产专用的图层、并生成一颗对应的图层树。 结果是:最终每一个节点都会直接或间接的从属于一个层。 满足分层有两个条件:用于层叠上下文属性(定位属性、透明属性、滤镜等)和需要裁剪的地方(overflow)
将图层绘制命令拆分为很多小的绘制指令,然后一个个执行这些命令,汇总成待绘制列表。
有了这些绘制命令、浏览器渲染引擎中的合成线程进行绘制。 所谓栅格化操作就是按照视口附近的图块优先生成位图。
浏览器进程通过via组件,接收合成线程发过来的绘制图块命令,将页面绘制到内存中,显示在屏幕上。
改变了元素长宽、浏览器会出发重新布局–后续一系列过程,会更新完成的渲染流水线,因此开销最大。
改变了颜色等未引起几何位置变化,重绘相较重排、省去了布局和分层阶段,因此效率会高一些。
比如CSS的transform实现的动画效果,可以避开重排和重绘阶段,大大提升绘制效率。