概览
这是一道前端经典的面试,面试被问率谁面谁知道,话不多说先上个人答案,主要分为以下几个环节:
- URL解析:浏览器将输入的URL解析出协议、主机名(域名)、端口号和查询参数等内容。
- DNS解析:浏览器会根据主机名(域名)通过DNS解析找到对应的IP地址。
- 建立TCP连接:浏览器找到对应的IP地址后一般通过三次握手与服务器建立TCP连接。
- 发起HTTP请求:TCP连接建立后浏览器会向服务器发送HTTP请求。
- 服务器处理请求:Web服务器接收到浏览器的HTTP请求后,根据请求的路径和方法,处理请求并返回相应的HTTP响应。
- 浏览器解析渲染页面:浏览器收到服务器的响应后,开始处理这个响应。浏览器会根据响应体中的HTML内容开始解析和渲染页面。这包括解析HTML结构、加载CSS和JavaScript文件,构建DOM树、渲染页面样式、执行页面的JavaScript脚本等过程。
- 关闭TCP连接:在页面加载完成后,浏览器会关闭与Web服务器之间的TCP连接,除非该连接被HTTP Keep-Alive机制保持。
URL解析
浏览器通过解析输入的字符串来判断当前输入是URL还是搜索关键字。当协议或者主机名不合法时,即认为输入的不是URL,这时浏览器会将输入的文字传给默认的搜索引擎。如果是合法的URL,浏览器则会解析出URL中的协议、主机名(域名)、端口和查询参数等。
DNS解析
在互联网体系中,主要是使用IP来进行网络设备标识,但是数字IP不便于记忆,便使用域名来代替IP,因此还需要将域名转化为真正服务器所在的IP来获取资源。简言之,浏览器会根据解析出的域名找出其对应的IP,其IP映射查找过程如下:
- 浏览器缓存中查找:浏览器首先会检查一下自身缓存中是否有对应的DNS记录。
- 系统缓存中查找:如果浏览器缓存中没有,浏览器会查看系统中的hosts文件,如果没有hosts文件,就查看系统本地的DNS缓存记录。
- 路由器缓存中查找:如果系统中也没有找到,则会发送个请求到路由器上,路由器再查询本身的缓存。
- 互联网服务提供商(Internet Service Provider 简称:ISP)缓存中查找:如果在路由器上也没有找到对应的域名IP,则会将这个请求ISP,每个ISP有相应的ISP DNS服务器。
建立TCP连接
当浏览器得到了目标服务器的IP地址,以及URL中给出来端口号(http协议默认端口号是80,https默认端口号是443),它会调用系统库函数socket,生产一个TCP流套接字。TCP套接字生产后,浏览器就会试着与对应的服务器创建TCP连接。建立TCP连接需要客户端和服务器经历以下三次握手:
- 第一次握手:客户端向服务端发生一个连接请求,证明自己的发送能力;
- 第二次握手:服务端接收到客户端连接请求后,向客户端发送一个应答,证明自己的接收能力和发送能力;
- 第三次握手:客户端接收到服务端的响应后发送一个确认请求,证明自己的接收能力; 具体过程如下图所示:
- ACK=0表示确认号无效,SYN=1表示这是一个连接请求或连接接受报文,同时表示这个数据报不能携带数据,seq=x表示客户端的初始序号,客户端进入syn_sent状态,即等待服务器的回复状态。
- 服务端监听到连接请求报文后,如同意建立连接,则向客户端发送确认。TCP报文首部中的SYN和ACK都置1 ,ack = x+1 表示期望收到对方下一个报文段的第一个数据字节序号是 x+1,同时表明x为止的所有数据都已正确收到(ack=1其实是ack=0+1,也就是期望客户端的第1个包),seq=y 表示服务端自己的初始序号(seq=0就代表这是服务器这边发出的第0号包)。这时服务器进入syn_rcvd,表示服务器已经收到客户端的连接请求,等待客户端的确认。
- 客户端收到确认后还需再次发送确认,同时携带要发送给服务端的数据。ACK置1表示确认号ack= y+1 有效(代表期望收到服务器的第1个包),Client自己的序号seq= x+1(表示这就是我的第1个包,相对于第0个包来说的),一旦收到客户端的确认之后,这个TCP连接就进入Established状态,就可以发起http请求了。
发起HTTP请求
TCP连接建立之后,就会发送一个http请求,http请求报文格式如下:
起始行:如 GET / HTTP/1.0 (请求的方法 请求的URL 请求所使用的协议)
头部信息:User-Agent Host等成键值对出现
主体
服务器处理请求
服务器接收到客户端的资源请求后,解析出请求中的请求方法、域名、请求路径等信息后,如资源存在则将对应的资源返回客户端,如不存在则响应404等,常见响应码及其含义如下:
- 200: 请求成功响应。
- 301: 请求资源重定向。
- 304: 请求资源未更改,根据缓存机制进入协商缓存。
- 401: 没权限,需进行身份验证。
- 403: 服务器拒绝响应。
- 404: 资源不存在。
- 500: 服务器内部错误。
浏览器解析渲染页面
浏览器获取服务端响应后,渲染步骤大致可以分为以下几步:
- 解析HTML,构建DOM树
- 解析CSS,生成CSS规则树
- 解析JS
- 合并DOM树和CSS规则树,生成渲染树
- 布局渲染树
- 绘制渲染树
1. 解析HTML,构建DOM树
浏览器解析HTML,构建DOM树,DOM树是以DOM元素以及属性为节点的树。DOM是文档对象模型(Document Object Model)的缩写,它是HTML文档的对象表示,同时也是HTML元素面向外部(如Javascript)的接口。树的根部是”Document”对象。整个DOM和HTML文档几乎是一对一的关系。如下图:
解析结束之后,浏览器开始加载网页的外部资源(CSS,图像,Javascript文件等)。此时浏览器把文档标记为可交互的(interactive),浏览器开始解析处于“推迟(deferred)”模式的脚本,也就是那些需要在文档解析完毕之后再执行的脚本。之后文档的状态会变为“完成(complete)”,浏览器会触发“加载(load)”事件。
注意解析 HTML 网页时永远不会出现“无效语法(Invalid Syntax)”错误,浏览器会修复所有错误内容,然后继续解析。
解析过程中,如果是遇到要加载外部css文件,浏览器会另外发出一个请求,来获取css文件。遇到图片资源,浏览器也会另外发出一个请求,来获取图片资源。这是异步请求,并不会影响html文档进行加载。但是当文档加载过程中遇到js文件,html文档会挂起渲染(加载解析渲染同步)的线程,不仅要等待文档中js文件加载完毕,还要等待解析执行完毕,才可以恢复html文档的渲染线程。
2. 解析CSS,生成CSS规则树
CSS解析过程与HTML解析过程类似,浏览器根据CSS词法和句法分析CSS文件和style标签包含的内容以及style属性的值。每个CSS文件都被解析成一个样式表对象(StyleSheet object),这个对象里包含了带有选择器的CSS规则,和对应CSS语法的对象。如下图:
3. 解析JS
浏览器UI线程是单线程,大多数浏览器的javascrip执行和更新界面共用一个线程。
js阻塞页面:浏览器里的http请求被阻塞一般都是由js所引起,具体原因是js文件在下载完毕之后会立即执行,而js执行时候会阻塞浏览器的其他行为,有一段时间是没有网络请求被处理的,这段时间过后http请求才会接着执行,这段空闲时间就是所谓的http请求被阻塞。
js阻塞原因:页面加载的规则是顺序执行,由于js也能控制UI展示,所以在遇到js代码时,UI线程会优先执行js。
4. 合并DOM树和CSS规则,生成render树
当DOM树和CSSOM都有了后,就开始构建渲染树(render树)了。一般来说,渲染树和DOM树相对应的,但不是严格意义上的一一对应,因为有一些不可见的DOM元素不会插入到渲染树中,如head这种不可见的标签或者display: none等,遍历DOM节点上创建渲染树,并计算每个节点的CSS样式值。过程大致如下:
5. 布局render树(Layout/reflow),负责各元素尺寸、位置的计算
通过渲染树中渲染对象的信息,计算出每一个渲染对象的位置和尺寸,部分规则如下:
- 通过累加子节点的宽度,该节点的水平内边距(padding)、边框(border)和外边距(margin),自底向上的计算”Frame 树”中每个节点的首选(preferred)宽度
- 通过自顶向下的给每个节点的子节点分配可行宽度,计算每个节点的实际宽度
- 通过应用文字折行、累加子节点的高度和此节点的内边距(padding)、边框(border)和外边距(margin),自底向上的计算每个节点的高度
- 使用上面的计算结果构建每个节点的坐标
- 当存在元素使用 floated,位置有 absolutely 或 relatively 属性的时候,会有更多复杂的计算,详见dev.w3.org/csswg/css2/ 和 www.w3.org/Style/CSS/c…
- 创建layer(层)来表示页面中的哪些部分可以成组的被绘制,而不用被重新栅格化处理。每个帧对象都被分配给一个层
- 页面上的每个层都被分配了Textures
- 每个层的帧对象都会被遍历,计算机执行绘图命令绘制各个层,此过程可能由CPU执行栅格化处理,或者直接通过D2D/SkiaGL在GPU上绘制
- 上面所有步骤都可能利用到最近一次页面渲染时计算出来的各个值,这样可以减少不少计算量
- 计算出各个层的最终位置,一组命令由 Direct3D/OpenGL发出,GPU命令缓冲区清空,命令传至GPU并异步渲染,帧被送到Window Server。
6. 绘制render树(paint),绘制页面像素信息
在渲染过程中,图形处理层可能使用通用用途的CPU,也可能使用图形处理器GPU,当使用GPU用于图形渲染时,图形驱动软件会把任务分成多个部分,这样可以充分利用 GPU 强大的并行计算能力,用于在渲染过程中进行大量的浮点计算。
绘制阶段,系统会遍历呈现树,并调用呈现器的“paint”方法,将呈现器的内容显示在屏幕上。如下图:
这张图片中重要的四个步骤
- 计算CSS样式
- 构建渲染树
- 布局,主要定位坐标和大小,是否换行,各种position overflow z-index属性
- 绘制,将图像绘制出来
- Layout,也称为Reflow,即回流。一般意味着元素的内容、结构、位置或尺寸发生了变化,需要重新计算样式和渲染树。
- Repaint,即重绘。意味着元素发生的改变只是影响了元素的一些外观之类的时候(例如,背景色,边框颜色,文字颜色等),此时只需要应用新样式绘制这个元素就可以了。
总结起来就是: 解析HTML以构建DOM树 –> 构建渲染树 –> 布局渲染树 –> 绘制渲染树。
关闭TCP连接
资源请求完毕之后,客户端和服务端之间会进行四次挥手来结束本次连接以释放资源。四次挥手过程如下图:
- 第一次挥手:浏览器发起,告知服务器请求发送完毕,服务器可以准备关闭。 浏览器发送报文FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
- 第二次挥手:服务器发起,告知浏览器收到关闭通知,浏览器也可以准备关闭。 服务端发送报文ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态,浏览器就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文。此时浏览器已经没有数据要发送了,但是服务器若发送数据,浏览器依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
- 第三次挥手:服务器发起,告知浏览器响应资源发送完毕,告知浏览器准备关闭。 服务器发送报文,1. FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待浏览器的确认。
- 第四次挥手:浏览器发起,告知服务器接收完毕,并准备关闭 浏览器发送确认报文,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,浏览器就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2MSL(最长报文段寿命)的时间后,当浏览器撤销相应的TCB后,才进入CLOSED状态。服务器只要收到了客户端发出的确认,立即进入CLOSED状态。