温故而知新:网页在浏览器中是怎么加载显示的?

94 阅读6分钟

前言

🙋‍ 知其然,更知其所以然,举一反三,融会贯通

我们从浏览器访问一个网址,然后就能看到对应的网页信息,这个看似简单的操作,实际由浏览器的各个模块协作完成。

浏览器的进程和线程

  • 进程是操作系统资源分配的基本单位,进程中包含线程
  • 线程是由进程所管理的

为了保证和提升浏览器稳定性和安全性,浏览器采用了多进程和多线程模型。

79a4bf6298164ff39a7ac978112f7e31_tplv-k3u1fbpfcp-zoom-in-crop-mark_4536_0_0_0.awebp

浏览器的进程:

  • 浏览器进程:负责界面的的显示,用户交互,子进程管理,提供存储等
  • 渲染进程:浏览器中每个的Tab页卡都有单独的渲染进程,核心用于渲染界面
  • 网络进程:主要处理网络资源加载(html,css,js等)
  • GPU进程:主要负责3D绘制,提高性能
  • 插件进程:负责管理维护chrome浏览器中安装的插件(内置或第三方的)

渲染进程中的线程:

  • GUI渲染线程:负责渲染页面,解析html和css,构建dom树,cssom树,渲染树和绘制页面,重绘和重排也在该线程执行

  • js引擎线程:一个tab页签中只有一个JS引擎线程(单线程),负责解析和执行js,它和GUI渲染线程不能同时执行,只能一个一个来,如果js执行时间过长就在阻塞界面的渲染

  • 异步http请求线程:XMLHttpRequest连接后浏览器开的一个线程,比如请求有回调函数,异步线程就会将回调函数加入到事件队列,等待js引擎空闲时执行

  • 计时器线程:主要指的是setTimeout和setInterval, 因为js引擎是单线程,如果处于阻塞状态,那么计时器就会不准了,所以需要单独的线程来处理计时工作。

  • 事件触发线程:主要用来控制事件循环,比如js执行定时器,或异步请求时,会将回调函数添加到事件队列中,等待js引擎空闲时,事件触发线程就会将队列中的任务拿到主线程(js引擎线程)去执行

网页加载过程

以第一次加载网页为例(没有缓存),主要的步骤如下:

1. DNS解析

浏览器会通过DNS服务器解析域名得到对应的IP地址

2. Tcp连接

通过Ip和端口与服务器之间建立Tcp连接,这个过程通常称为三次握手

16802443da610767_tplv-t2oaga2asx-zoom-in-crop-mark_4536_0_0_0.awebp

第一次握手

开始建立连接:客户端发送连接请求报文段,设置SYN=1, Seq=x(x为一个随机数)。客户端端进入SYN_SEND状态,等待服务器的确认

第二次握手

服务器收到SYN报文段:服务器收到客户端的SYN报文段后,会进行回执,此时服务器会给客户端发送SYN=1,Seq=y,Ack = x + 1的报文段,已确认建立的是同一个连接

第三次握手

客户端收到SYN+ACK报文段:客户端收到服务器的SYN+ACK报文段后,会向服务器再次发送ACK报文段,设置ACK=y + 1, 服务器收到后确认完毕,客户端和服务器端都进入ESTABLISHED状态,完成Tcp三次握手

3. Http请求

创建Tcp连接后,浏览器就可以向服务器发送Http请求,服务器接收到请求之后根据url的路径进行处理,然后把Html资源文件返回给浏览器,这时浏览器就可以开始解析Html文档了。

4. 断开Tcp连接

为了避免服务器与客户端双方的资源占用和损耗,当双方没有请求和响应时,任意一方都可以主动的发送关闭请求,这个过程通常称为四次挥手

1680216537e7fbf0_tplv-t2oaga2asx-zoom-in-crop-mark_4536_0_0_0.awebp

第一次挥手

浏览器客户端主动向服务器发送报文段FIN, seq = x+2, ACK= y +1, 告诉服务器我已经没有数据要发送了,要断开连接,此时不能确定服务器是否还会给客户端端发送消息

第二次挥手

服务器收到客户端的报文段后,会给客户端一个回执消息,设置ACK = x + 3, 告诉客户端我已经收到你的断开消息

第三次挥手

在第二次挥手的基础上,服务器同时也会发送一个报文段为FIN seq = y + 1的消息,告诉客户端我也没有消息发送了,要断开连接

第四次挥手

客户端收到FIN的报文段后,会再次给服务器发送一个ACK= y + 2的报文段,告诉服务器我也收到你断开的消息,那我们就断开吧

5.浏览器解析渲染

先看一张图了解一下渲染的过程

e31fd940e8b74c7597120d06c852fc55.png

主要流程分为如下几个步骤:

  1. 解析HTML,构建DOM树(文档对象模型树)
  2. 解析CSS, 构建CSSOM树(css规则树)
  3. 合并DOM树和CSS树,生成render树(渲染树或者叫布局树)
  4. 根据render树进行布局(layout/reflow), 计算每个元素的位置以及尺寸
  5. 通过布局树,进行分层(根据定位属性,透明属性,transform属性,clip属性等)生成图层树
  6. 将不同的图层进行绘制,然后浏览器将不同的图层信息发送给GPU,GPU将各图层合成, 最终显示在网页上

解析过程中细节问题:

  • 服务器端返回的类型是text/html时,浏览器会将收到的数据通过HTMLParser进行解析(边下载边解析)
  • 在解析前会执行预解析操作,会预先加载js,css文件
  • 然后依次是字节流-->分词器-->Tokens-->根据token生成节点-->插入到DOM树中
  • 遇到js:如果在解析过程中遇到script标签,HTMLParse则会停止解析,浏览器会去下载并执行对应的js脚本
  • 在js执行前,需要等待当前脚本之上的所有css加载解析完毕(js的执行依赖css的加载)

css样式文件尽量放在页面头部,css加载不会阻塞DOM Tree解析,浏览器会用解析的DOM Tree和CSSOM进行渲染,这样不会出现页面闪烁问题,如果css放在页面底部,浏览器是边解析边渲染的,渲染出的结果不包含样式,后续会发生重绘操作。

js文件放在页面底部,防止js的加载,解析,执行阻塞页面后续的正常渲染