浏览器从输入URL到页面渲染加载的过程(浏览器知识体系整理)


前言

写这篇文章的初衷

记得最开始学前端知识时是一点一点的积累一个知识点一个知识点的攻克。
就这样虽然在很长一段时间内积累了不少的知识但是总是无法将它串联到一起。每次梳理时都是很分散的无法保持思路连贯性。
直到最近在将DNS域名解析、建立TCP连接、构建HTTP请求、浏览器渲染过程‘’流程梳理一遍后感觉就跟打通了任督二脉一样有了一个整体的架构以前的知识点都连贯起来了至少现在知道了它的大部分骨架。
梳理出一个知识体系以后就算再学新的知识也会尽量往这个体系上靠拢环环相扣更容易理解也更不容易遗忘。这也是本文的目标。

前端领域 的知识为重点并且本文内容超多建议先了解主干然后分批次阅读。
这篇文章真的写了好久好久…


一、梳理主干流程

知识体系中最重要的是骨架脉络。有了骨架后才方便填充细节。所以先梳理下主干流程

  1. 浏览器接收url并开启一个新进程这一部分可以展开浏览器的进程与线程的关系
  2. 浏览器解析输入的 URL提取出其中的协议、域名和路径等信息。这部分涉及URL组成部分
  3. 浏览器向 DNS 服务器发送请求DNS服务器通过 多层查询 将该 域名 解析为对应的 IP地址 然后将请求发送到该IP地址上与 服务器 建立连接和交换数据。这部分涉及DNS查询
  4. 浏览器与服务器建立 TCP 连接。这部分涉及TCP三次握手/四次挥手/5层网络协议
  5. 浏览器向服务器发送 HTTP 请求包含请求头和请求体。4,5,6,7包含http头部、响应码、报文结构、cookie等知识
  6. 服务器接收并处理请求并返回响应数据包含状态码、响应头和响应体。
  7. 浏览器接收到响应数据解析响应头和响应体并根据状态码判断是否成功。
  8. 如果响应成功浏览器接收到http数据包后的解析流程这部分涉及到html - 词法分析解析成DOM树解析CSS生成CSSOM树样式树合并生成render渲染树。然后layout布局painting渲染复合图层合成调用GPU绘制等再显示在屏幕上。这个过程会发生回流和重绘。
  9. 连接结束 -> 断开TCP连接 四次挥手

梳理出主干骨架然后就需要往骨架上填充细节内容。


二、浏览器接收url并开启一个新进程

1. 浏览器是多进程的

浏览器是多进程的有一个主进程每打开一个tab页面都会新开一个进程某些情况下多个tab会合并进程。

注意在这里浏览器应该也有自己的优化机制有时候打开多个tab页后比如打开多个空白标签页。可以在Chrome任务管理器中看到进程被合并了。

进程可能包括主进程插件进程GPUtab页浏览器内核等等。

  • Browser进程浏览器的主进程负责协调、主控只有一个。
  • 第三方插件进程每种类型的插件对应一个进程仅当使用该插件时才创建。
  • GPU进程最多一个用于3D绘制等。
  • 浏览器渲染进程浏览器内核内部是多线程的默认每个Tab页面一个进程互不影响。作用是页面渲染脚本执行事件处理等。浏览器有时候会优化如多个空白页合并成一个进程

强化记忆在浏览器中打开一个网页相当于新起了一个进程进程内有自己的多线程

下图以 chrome浏览器 为例。我们可以自己通过Chrome的更多工具 =》 任务管理器 自行验证查看可以看到chrome的任务管理器中有多个进程分别是每一个Tab页面有一个独立的进程以及一个主进程
然后能看到每个进程的内存资源信息以及cpu占有率。

浏览器多进程图示

2. 浏览器内核是多线程的

每一个tab页面可以看作是浏览器内核的一个进程然后这个进程是多线程的它有几大类子线程

  • GUI渲染线程负责渲染浏览器界面解析HTMLCSS构建DOM树和RenderObject树布局和绘制等。GUI渲染线程与JS引擎线程是互斥的
  • JS引擎线程也叫 JS 内核负责解析执行 JS 脚本程序的主线程例如 V8 引擎。JS引擎一直等待着任务队列中任务的到来然后加以处理一个Tab页renderer进程中无论什么时候都只有一个JS线程在运行JS程序。
  • 事件触发线程属于浏览器内核线程主要用于控制事件例如鼠标、键盘等当事件被触发时就会把事件的处理函数推进事件队列等待 JS 引擎线程执行。
  • 定时器触发线程主要控制 setInterval和 setTimeout用来计时计时完毕后则把定时器的处理函数推进事件队列中等待 JS 引擎线程。
  • 异步http请求线程通过XMLHttpRequest连接后通过浏览器新开的一个线程监控readyState状态变更时如果设置了该状态的回调函数则将该状态的处理函数推进事件队列中等待JS引擎线程执行。

在这里插入图片描述

可以看到里面的JS引擎是内核进程中的一个线程这也是为什么常说JS引擎是单线程的。

虽然 JS 是单线程的但实际上参与工作的线程一共有四个
在这里插入图片描述

后面三个只是协助只有 JS 引擎线程是真正执行的。

3. JS引擎单线程的原因

JS引擎之所以是单线程是由于JavaScript最初是作为浏览器脚本语言开发的并且JavaScript需要操作DOM等浏览器的API如果多个线程同时进行DOM更新等操作则可能会出现各种问题如竞态条件、数据难以同步、复杂的锁逻辑等因此将JS引擎设计成单线程的形式就可以避免这些问题。
虽然JS引擎是单线程的但是通过使用 异步编程模型事件循环机制JS仍然可以实现高并发处理。

如果JS是多线程的场景描述:
那么现在有2个线程process1 process2由于是多线程的JS所以他们对同一个dom同时进行操作
process1 删除了该dom而process2 编辑了该dom同时下达2个矛盾的命令浏览器究竟该如何执行呢这时可能就会出现问题了。

4. GUI渲染线程与JS引擎线程互斥

由于JavaScript是可操纵DOM的如果在修改这些元素属性同时渲染界面即JS线程和UI线程同时运行那么渲染线程前后获得的元素数据就可能不一致了。

因此为了防止渲染出现不可预期的结果浏览器设置GUI渲染线程与JS引擎为互斥的关系当JS引擎执行时GUI线程会被挂起
GUI更新则会被保存在一个队列中等到JS引擎线程空闲时立即被执行。

因为本文主要讲输入URL后页面的渲染过程所以关于浏览器开启网络请求线程这部分详细内容大家可以移步查看里面包括JS运行机制进程线程的详解
从浏览器多进程到JS单线程JS运行机制最全面的一次梳理


二、解析URL

输入URL后会进行解析URL的本质就是统一资源定位符

URL一般包括几大部分

  1. 协议Protocol指访问资源时使用的协议常见的协议有 HTTP、HTTPS、FTP 等。
  2. 主机名Host指服务器的域名或 IP 地址用于唯一标识一个服务器。
  3. 端口号Port指服务器上提供服务的端口号可以省略。例如默认的 HTTP 端口为 80HTTPS 端口为 443。
  4. 路径Path指服务器上资源的路径表示访问资源时需要进入的目录层级以及资源的名称。
  5. 查询参数Query指对资源请求的参数格式为 key=value多个参数间使用 & 连接。
  6. 锚点Fragment指 # 后的hash值一般用来定位到某个位置。

举个例子www.example.com/index.html?key1=value1&key2=value2#section 表示了一个 URL
其中协议为 HTTP主机名为 www.example.com路径为 /index.html查询参数为 key1=value1 和 key2=value2锚点为 section。


三、DNS域名解析

在解析过程之前我们先理解几个概念。

1. DNS是什么

DNSDomain Name System是一种用于将域名解析为IP地址的系统。把我们的域名映射为IP地址这就是DNS的作用
它可以将人们易于记忆的域名转换为服务器可识别的IP地址这样用户就可以使用域名访问网站而不必直接输入数字格式的IP地址。

在浏览器中输入网址时电脑会先向DNS服务器发送请求获取该网址对应的IP地址并在成功获取后直接连接该IP地址对应的服务器服务器端获取网页内容并显示出来完成整个访问过程。因此DNS在互联网中起着至关重要的作用。

2. IP和域名的关系

IPInternet Protocol地址是一个数字标识用于唯一识别连接到互联网上的每个计算机、服务器和其他设备。域名则是网站的人类可读的名称。域名系统DNS服务器可以将域名转换为与之关联的IP地址。
简单来说IP地址是网络设备的标识符而域名则是方便人们记忆和使用的网络地址别名。
域名系统通过将 域名 映射到 IP地址使互联网上的用户能够以易记的方式访问特定的网站或服务器。

3. 域名服务器概念图

在这里插入图片描述

从上面这张图可以看到域名的管理是分层次的。最高级是根也叫做根服务器。从上往下功能逐渐细化。DNS就是和这些服务器进行打交道。
有了上面的这些概念现在我们再来认识一下DNS域名解析过程就容易多了。

4. DNS域名解析过程

  1. 本地计算机缓存中查询是否有该域名对应的IP地址若有则直接返回解析过程结束。
  2. 如果本地计算机缓存中没有该域名对应的IP地址则向本地DNS服务器发送查询请求。
  3. 如果本地DNS服务器缓存中有该域名对应的IP地址则直接返回解析过程结束。
  4. 如果本地DNS服务器缓存中没有该域名对应的IP地址则向根域名服务器发送查询请求。
  5. 根域名服务器返回下一级DNS服务器的地址顶级域名服务器。
  6. 本地DNS服务器向顶级域名服务器发送查询请求。
  7. 顶级域名服务器返回下一级DNS服务器的地址权威DNS服务器。
  8. 本地DNS服务器向权威DNS服务器发送查询请求。
  9. 权威DNS服务器返回该域名对应的IP地址并将结果返回给本地DNS服务器。
  10. 本地DNS服务器将结果保存在缓存中并将结果返回给用户所在的计算机。
  11. 用户所在的计算机将结果保存在缓存中并使用该IP地址访问对应的网站。

这个过程大体大体由一张图可以表示从网上找的图片方便理解。
而且需要知道dns解析是很耗时的因此如果解析域名过多会让首屏加载变得过慢可以考虑dns-prefetch优化

在这里插入图片描述

关于 本地DNS服务器 这里单独讲解下

如果之前的过程无法解析时操作系统会把这个域名发送给这个本地DNS服务器。每个完整的内网通常都会配置本地DNS服务器例如用户是在学校或工作单位接入互联网那么用户的本地DNS服务器肯定在学校或工作单位里面。它们一般都会缓存域名解析结果当然缓存时间是受到域名的失效时间控制的。大约80%的域名解析到这里就结束了后续的DNS迭代和递归也是由本地DNS服务器负责。

5. DNS解析时发现域名和IP不一致访问了该域名会如何

  • 域名和IP不一致域名解析成了其他的的IP地址但是这个IP地址正确。访问该域名就会访问其他的网站。

知乎上有一个阿里巴巴的回答
从技术上来讲是可以解析到任意IP地址的这时候针对这个地址发起HTTP访问HTTP头中的host字段会是你的域名而非该IP对应站点的域名如果对方的网站HTTP服务器没有做对应的防护就可以访问如果对方的网站HTTP服务器有防护则无法访问

  • 域名和IP不一致域名解析成了其他的的IP地址但是这个IP地址错误访问该域名就会失败。

可参考DNS解析时发现域名和IP不一致访问了该域名会如何大厂真题


四、建立 TCP 连接

需要了解3次握手规则建立连接以及断开连接时的四次挥手。

拿到了IP地址后就可以发起HTTP请求了。HTTP请求的本质就是TCP/IP的请求构建。建立连接时需要 3次握手 进行验证断开链接也同样需要 4次挥手 进行验证保证传输的可靠性。

1. 三次握手

模拟三次握手场景对话版

客户端hello你是server么
服务端hello我是server你是client么
客户端yes我是client

可通过下方图文结合方式字理解三次握手
在这里插入图片描述
三次握手​​​​​​​原理

第一次握手客户端发送一个带有 SYNsynchronize同步标志的数据包给服务端。
第二次握手服务端接收成功后回传一个带有 SYN/ACK 标志的数据包传递确认信息表示我收到了。
第三次握手客户端再回传一个带有 ACK 标志的数据包表示我知道了握手结束。

其中SYN标志位数置1表示建立TCP连接ACK表示响应置1时表示响应确认。

三次握手过程详细说明
刚开始客户端处于 Closed 的状态服务端处于 Listen 状态。

  1. 第一次握手: 客户端发送标识位SYN = 1随机产生序列号seq = x的数据包到服务端服务端由SYN = 1知道客户端要建立连接并进入SYN_SENT状态等待服务器确认SYN=1seq=xx为随机生成的数值
  2. 第二次握手: 服务器收到请求并确认联机信息后向客户端发送标识位SYN = 1ACK = 1和随机产生的序列号seq = y, 确认码ack number = x+1客户端发送的seq+1的数据包此时服务器进入SYN_RCVD状态SYN=1ACK=1seq=yy为随机生成的数值确认号 ack=x+1这里ack加1可以理解为时确认和谁建立连接。
  3. 第三次握手客户端收到后检查确认码ack number是否正确即和第一次握手发送的序列号加1结果是否相等以及ACK标识位是否为1若正确客户端发送标识位ACK = 1、seq = x + 1和确认码ack = y + 1服务器发送的seq+1到服务器服务器收到后确认ACK=1和seq是否正确若正确则完成建立连接此包发送完毕客户端和服务器进入ESTAB_LISHED状态。完成三次握手客户端与服务器开始传送数据.。ACK=1seq=x+1ack=y+1

TCP 三次握手的建立连接的过程就是相互确认初始序号的过程。告诉对方什么样序号的报文段能够被正确接收。
第三次握手的作用是 客户端对服务器端的初始序列号的确认如果只使用两次握手那么服务器就没有办法知道自己的序号是否已被确认。同时这样也是为了防止失效的请求报文被服务器接收而出现错误的情况。

2. 四次握手

模拟四次挥手场景对话版

主动方我已经关闭了向你那边的主动通道了只能被动接收了
被动方收到通道关闭的信息我这还有数据没有发送完成你等下
被动方那我也告诉你我这边向你的主动通道也关闭了
主动方最后收到数据之后双方无法通信

可通过下方图文结合方式理解四次挥手​​​​​​​
在这里插入图片描述

四次挥手​​​​​​​原理

第一次挥手客户端发送一个FIN用来关闭客户端到服务器的数据传送并且指定一个序列号。客户端进入FIN_WAIT_1状态。
第二次挥手服务器收到FIN后发送一个ACK给客户端确认序号为客户端的序列号值 +1 表明已经收到客户端的报文了此时服务器处于 CLOSE_WAIT 状态。
第三次挥手服务器发送一个FIN用来关闭服务器到客户端的数据传送服务器进入LAST_ACK状态。
第四次挥手客户端收到FIN后客户端进入TIME_WAIT状态接着发送一个ACK给服务器确认序号为收到序号+1 服务器进入CLOSED状态完成四次挥手。

其中FIN标志位数置1表示断开TCP连接。

四次挥手过程详细说明

刚开始双方都处于 ESTABLISHED 状态假如是客户端先发起关闭请求。

  1. 第一次挥手客户端发送一个FIN = 1、初始化序列号seq = u到服务器表示需要断开TCP连接客户端进入FIN_WAIT_1状态等待服务器的确认。FIN = 1seq = uu由客户端随机生成
  2. 第二次挥手服务器收到这个FIN它发回ACK = 1、seq序列号由回复端随机生成、确认序号ack为收到的序号加1ack = u+1以便客户端收到信息时知晓自己的TCP断开请求已经得到验证。服务器进入CLOSE_WAIT等待关闭连接客户端进入FIN_WAIT_2稍后关闭连接。ACK = 1seq = vack = u+1
  3. 第三次挥手服务器在回复完客户端的TCP断开请求后不会马上进行TCP连接的断开。服务器会先确保断开前所有传输到客户端的数据是否已经传输完毕一旦确认传输完毕就会发回FIN = 1ACK = 1,seq = w和确认码ack = u+1给客户端服务器进入LAST_ACK 状态等待最后一次ACK确认;FIN = 1ACK = 1,seq = wack = u+1 w由服务器端随机生成
  4. 第四次挥手客户端收到服务器的TCP断开请求后会回复服务器的断开请求。包含ACK = 1、随机生成的seq = u+1并将确认序号设置为收到序号加1ack = w+1到服务器从而完成服务器请求的验证回复。客户端进入TIME-WAIT 状态此时 TCP 未释放掉需要等待 2MSL 以确保服务器收到自己的 ACK 报文后进入CLOSE状态服务端进入CLOSE状态。ACK = 1seq = u+1ack = w+1

注意为什么 TIME_WAIT 等待的时间是 2MSL
1MSL 是 报文最大生存时间一来一回需要等待 2 倍的时间。
2最后一次挥手中客户端会等待一段时间再关闭的原因是为了防止发送给服务器的确认报文段丢失或者出错从而导致服务器 端不能正常关闭。

常用关键词总结

  • SYN标志位用来建立TCP连接。如果SYN=1而ACK=0表明它是一个连接请求如果SYN=1且ACK=1则表示同意建立一个连接。
  • ACK表示响应置1时表示确认号为合法为0的时候表示数据段不包含确认信息确认号被忽略。)
  • FIN表示关闭连接置1时表示发端完成发送任务。用来释放连接表明发送方已经没有数据发送了。

为什么需要四次挥手呢

  1. TCP协议 的连接是全双工的即数据传输可以同时在两个方向上进行。所以终止连接时需要每个方向都单独关闭。单独一方的连接关闭只代表不能再向对方发送数据连接处于的是半关闭状态
  2. 客户端发送FIN报文终止连接后服务器可能还有数据需要发送比如上一次的响应所以服务器会先发送ACK报文确认收到FIN报文并将未发送的数据发送出去然后再发送自己的FIN报文终止连接。
  3. 客户端接收到服务器的FIN报文后也需要发送ACK报文确认收到才能正式关闭连接。

3. 为什么是三次握手不是两次、四次

为了确认双方的 接收能力发送能力 都正常。

如果是用两次握手则会出现下面这种情况
如客户端发出连接请求但因连接请求报文丢失而未收到确认于是客户端再重传一次连接请求。后来收到了确认建立了连接。数据传输完毕后就释放了连接此时客户端共发出了两个连接请求报文段。
其中第一个丢失第二个到达了服务端但是第一个丢失的报文段只是在某些网络节点长时间滞留了延误到连接释放以后的某个时间才到达服务端此时服务端误以为客户端又发出一次新的连接请求于是就向客户端发出确认报文段同意建立连接不采用三次握手只要服务端发出确认就建立新的连接了。此时客户端忽略服务端发来的确认也不发送数据则服务端一直等待客户端发送数据浪费了资源。

4. 五层因特网协议栈

这个概念挺难记全的这里先对它有个整体概念就好。

其实就是一个概念从客户端发出HTTP请求到服务器接收中间会经过一系列的流程。

简括就是

从应用层发送HTTP请求到传输层通过三次握手建立tcp/ip连接再到网络层的ip寻址再到数据链路层的封装成帧最后到物理层的利用物理介质传输。

当然服务端的接收就是反过来的步骤。

五层因特网协议栈其实就是由上往下

  1. 应用层DNSHTTPDNS将域名解析成IP地址并发送HTTP请求OSI 参考模型中最靠近用户的一层。
  2. 传输层TCPUDP 建立TCP连接三次握手客户端和服务端数据传输就是在这层进行的。
  3. 网络层IPARP地址解析协议IP寻址及路由选择
  4. 数据链路层封装成帧
  5. 物理层(利用物理介质传输比特流) 物理传输然后传输的时候通过双绞线电磁波等各种介质

其实也有一个完整的OSI七层框架与之相比多了会话层、表示层。

OSI七层框架从上到下分别是 应用层表示层会话层传输层网络层数据链路层物理层

表示层主要处理两个通信系统中交换信息的表示方式包括数据格式交换数据加密与解密数据压缩与终端类型转换等。
会话层它具体管理不同用户和进程之间的对话如控制登陆和注销过程

OSI七层框架它的缺点是分层太多增加了网络工作的复杂性所以没有大规模应用。后来人们对 OSI 进行了简化合并了一些层最终只保留了 4 层

1.应用层会话层表示层应用层
2.传输层
3.网络层
4.网络接口层 数据链路层物理层

引用网上的图例解释

在这里插入图片描述


五、浏览器向服务器发送 HTTP 请求

1. HTTP请求报文都有什么组成

HTTP请求报文主要由三个部分组成请求行请求头请求体。具体如下

请求行包含请求方法URI请求的资源路径HTTP协议版本。例如GET /index.html HTTP/1.1。
请求头Header: 包含了客户端向服务器发送的附加信息例如浏览器类型、字符编码、认证信息等。请求头以键值对的形式存在多个键值对之间以换行符分隔。例如Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7。
请求体Body: 存放请求参数即浏览器向服务器传输数据的实体部分。常用于POST方法提交请求时发送表单数据、JSON数据等类型的数据。

需要注意的是并不是所有的HTTP请求都必须带有请求体像GET请求通常不需要发送请求体。

在这里插入图片描述

为什么 HTTP 报文中要存在 “空行”?
因为 HTTP 协议并没有规定报头部分的键值对有多少个。空行就相当于是 “报头的结束标记”, 或者是 “报头和正文之间的分隔符”。
HTTP 在传输层依赖 TCP 协议, TCP 是面向字节流的. 如果没有这个空行, 就会出现 “粘包问题”

2. 常见状态码含义

区分状态码
1××开头 - 信息性状态码表示HTTP请求已被接收需要进一步处理。
2××开头 - 成功状态码表示请求已成功处理完成。
3××开头 - 重定向状态码表示请求需要进一步的操作以完成。
4××开头 - 客户端错误状态码表示请求包含错误或无法完成。
5××开头 - 服务器错误状态码表示服务器无法完成有效的请求。

常见状态码
200 - 请求成功从客户端发送给服务器的请求被正常处理并返回

301 - 表示被请求的资源已经被永久移动到新的URI永久重定向
302 - 表示被请求的资源已经被临时移动到新的URI临时重定向
304 - 表示服务器资源未被修改通常是在客户端发出了一个条件请求服务器通过比较资源的修改时间来确定资源是否已被修改

400 - 服务器不理解请求请求报文中存在语法错误
401 - 请求需要身份验证
403 - 服务器拒绝请求访问权限出现问题
404 - 被请求的资源不存在
405 - 不允许的HTTP请求方法意味着正在使用的HTTP请求方法不被服务器允许

500 - 服务器内部错误无法完成请求
503 - 服务器当前无法处理请求一般是因为过载或维护

3. 请求/响应头部

请求和响应头部也是分析时常用到的。

常用的请求头部部分

Accept: 接收类型表示浏览器支持的MIME类型
对标服务端返回的Content-Type
Accept-Encoding浏览器支持的压缩类型,如gzip等,超出类型不能接收
Content-Type客户端发送出去实体内容的类型
Cache-Control: 指定请求和响应遵循的缓存机制如no-cache
If-Modified-Since对应服务端的Last-Modified用来匹配看文件是否变动只能精确到1s之内http1.0
Expires缓存控制在这个时间内不会请求直接使用缓存http1.0而且是服务端时间
Max-age代表资源在本地缓存多少秒有效时间内不会请求而是使用缓存http1.1中
If-None-Match对应服务端的ETag用来匹配文件内容是否改变非常精确http1.1中
Cookie: 有cookie并且同域访问时会自动带上
Connection: 当浏览器与服务器通信时对于长连接如何进行处理,如keep-alive
Host请求的服务器URL
Origin最初的请求是从哪里发起的只会精确到端口,OriginReferer更尊重隐私
Referer该页面的来源URL(适用于所有类型的请求会精确到详细页面地址csrf拦截常用到这个字段)
User-Agent用户客户端的一些必要信息如UA头部等

常用的响应头部部分

Access-Control-Allow-Headers: 服务器端允许的请求Headers
Access-Control-Allow-Methods: 服务器端允许的请求方法
Access-Control-Allow-Origin: 服务器端允许的请求Origin头部譬如为*
Content-Type服务端返回的实体内容的类型
Date数据从服务器发送的时间
Cache-Control告诉浏览器或其他客户什么环境可以安全的缓存文档
Last-Modified请求资源的最后修改时间
Expires应该在什么时候认为文档已经过期,从而不再缓存它
Max-age客户端的本地资源应该缓存多少秒开启了Cache-Control后有效
ETag请求变量的实体标签的当前值
Set-Cookie设置和页面关联的cookie服务器通过这个头部把cookie传给客户端
Keep-Alive如果客户端有keep-alive服务端也会有响应如timeout=38
Server服务器的一些相关信息

一般来说请求头部和响应头部是匹配分析的。
譬如请求头部的Accept要和响应头部的Content-Type匹配否则会报错。
譬如跨域请求时请求头部的Origin要匹配响应头部的Access-Control-Allow-Origin否则会报跨域错误。
譬如在使用缓存时请求头部的If-Modified-SinceIf-None-Match分别和响应头部的Last-ModifiedETag对应。

注意点

请求头 和 响应头 中的 Content-Type 是不一样的。

请求头的Content-Type常见取值

application/x-www-from-urlencoded  //以键值对的数据格式提交
multipart/form-data //用于上传文件图片等二进制数据

响应头的Content-Type常见取值

text/html // body 数据格式是 HTML
text/css  // body 数据格式是 CSS
application/javascript // body 数据格式是 JavaScript
application/json //body 数据格式是 JSON 最常见的

4. 请求/响应体

http 请求 时除了头部还有消息实体一般来说
请求实体中会将一些需要的参数都放入用于post请求。
比如实体中可以放参数的序列化形式a=1&b=2这种或者直接放表单对象Form Data 对象上传时可以夹杂参数以及文件等等。

而一般 响应实体中就是放服务端需要返给客户端的内容。
一般现在的接口请求时实体中就是信息的json格式而像页面请求这种里面直接放了一个html字符串然后浏览器自己解析并渲染。

如下图所示post请求发送给接口的数据

在这里插入图片描述

注意点

  1. 不是所有的HTTP请求都必须带有请求体像GET请求通常不需要发送请求体。
  2. 响应完成之后怎么办TCP 连接就断开了吗

不一定。这时候要判断Connection字段, 如果请求头或响应头中包含Connection: Keep-Alive
表示建立了持久连接这样TCP连接会一直保持之后请求统一站点的资源会复用这个连接。否则断开TCP连接, 请求-响应流程结束。

5. cookie以及优化

cookie是浏览器的一种本地存储方式一般用来帮助 客户端服务端 通信的常用来进行身份校验结合服务端的 session 使用。

场景如下简述

在登陆页面用户登陆了
此时服务端会生成一个sessionsession中有对应用户的信息如用户名、密码等
然后会有一个sessionid相当于是服务端的这个session对应的key
然后服务端在登录页面中写入cookie值就是: jsessionid=xxx
然后浏览器本地就有这个cookie了以后访问同域名下的页面时自动带上cookie自动检验在有效时间内无需二次登陆。

一般来说cookie是不允许存放敏感信息的千万不要明文存储用户名、密码因为非常不安全如果一定要强行存储首先一定要在cookie中设置httponly这样就无法通过js操作了另外可以考虑RSA等非对称加密因为实际上浏览器本地也是容易被攻克的并不安全
另外由于在同域名的资源请求时浏览器会默认带上本地的cookie针对这种情况在某些场景下是需要优化的。

比如以下场景

客户端在 域名A 下有cookie这个可以是登录时由服务端写入的
然后在 域名A 下有一个页面页面中有很多依赖的静态资源都是 域名A 的譬如有20个静态资源
此时就有一个问题页面加载请求这些静态资源时浏览器会默认带上 cookie
也就是说这20个静态资源的 http请求每一个都得带上 cookie而实际上静态资源并不需要 cookie 验证
此时就造成了较为严重的浪费而且也降低了访问速度因为内容更多了

针对这种场景是有优化方案的多域名拆分。具体做法就是

  • 将静态资源分组分别放到不同的域名下如static.base.com
  • page.base.com页面所在域名下请求时是不会带上 static.base.com 域名的cookie的所以就避免了浪费

说到了多域名拆分这里再提一个问题那就是

  • 在移动端如果请求的域名数过多会降低请求速度因为域名整套解析流程是很耗费时间的而且移动端一般带宽都比不上pc
  • 此时就需要用到一种优化方案dns-prefetch让浏览器空闲时提前解析dns域名不过也请合理使用勿滥用

关于cookie的交互可以看下图总结
在这里插入图片描述

6. HTTP协议各版本的区别

HTTP协议的版本历经多次更新迭代主要包括 HTTP/1.0HTTP/1.1HTTP/2等版本它们之间的主要区别如下

1HTTP/1.0

  1. 浏览器与服务器只保持短连接浏览器的每次请求都需要与服务器建立一个TCP连接都要经过三次握手四次挥手。
  2. 由于浏览器必须等待响应完成才能发起下一个请求造成 “队头阻塞”
    如果某请求一直不到达那么下一个请求就一直不发送。高延迟–带来页面加载速度的降低

2HTTP/1.1目前使用最广泛的版本

  1. 支持长连接通过Connection: keep-alive保持HTTP连接不断开避免重复建立TCP连接。
  2. 管道化传输通过长连接实现一个TCP连接中同时处理多个HTTP请求服务器会按照请求的顺序去返回响应的内容无法存在并行响应。http请求返回顺序按照服务器响应速度来排序这里也会引入promise.then 和 async await 来控制接口请求顺序
  3. 新增了一些请求方法新增了一些请求头和响应头如下
  4. 支持断点续传 新增 Range 和 Content-Range 头表示请求和响应的部分内容
  5. 加入缓存处理响应头新字段Expires、Cache-Control
  6. 增加了Host字段为了支持多虚拟主机的场景使用同一个IP地址上可以托管多个域名访问的都是同一个服务器从而满足HTTP协议发展所需要的更高级的特性。
  7. 并且添加了其他请求方法put、delete、options…

缺点

  1. 队头阻塞
  2. 无状态通信模型巨大的HTTP头部也就是服务器端不保存客户端请求的任何状态信息。这样会造成一些需求频繁交互的应用程序难以实现需要通过其他机制来保证状态的一致性等。
  3. 明文传输–不安全
  4. 不支持服务端推送

3HTTP/2.0

  1. 采用二进制格式而非文本格式
  2. 多路复用在同一个TCP连接上同时传输多条消息每个请求和响应都被分配了唯一的标识符称为“流Stream”这样每条信息就可以独立地在网络上传输。
  3. 使用 HPACK 算法报头压缩降低开销。
  4. 服务器推送支持服务器主动将相关资源预测性地推送给客户端以减少后续的请求和延迟。例如 HTML、CSS、JavaScript、图像和视频等文件

4HTTP3.0

是 HTTP/3 中的底层支撑协议该协议基于 UDP又取了 TCP 中的精华实现了即快又可靠的协议。

  1. 运输层由TCP改成使用UDP传输
  2. 队头堵塞问题的解决更为彻底
  3. 切换网络时的连接保持基于TCP的协议由于切换网络之后IP会改变因而之前的连接不可能继续保持。而基于UDP的QUIC协议则可以内建与TCP中不同的连接标识方法从而在网络完成切换之后恢复之前与服务器的连接
  4. 升级新的压缩算法

注意 HTTP 1.1起支持长连接keep-alive不会永远保持它有一个持续时间一般在服务器中配置如apache另外长连接需要客户端和服务器都支持时才有效

管道传输和多路复用的区别

HTTP/2 的多路复用可以理解为一条公路上同时行驶多辆车的场景每辆车对应一个请求或响应而公路对应一个 TCP 连接。
在 HTTP/1.x 中只能一辆车请求或响应通过这条公路其他车必须等待前面的车通过后再行驶
而在 HTTP/2 中则允许多辆车同时在这条公路上行驶它们之间不会互相干扰或阻塞从而提高了公路的使用效率和通行能


六、单独拎出来的缓存问题HTTP缓存策略

浏览器缓存的特点

  • 浏览器每次发起请求都会先在浏览器缓存中查找该请求的结果以及缓存标识
  • 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中

根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分分别是强缓存协商缓存

  1. 强缓存使用强缓存策略时如果缓存资源在过期时间内是的话直接从本地缓存中读取资源不与服务器进行通信。常见的缓存控制字段有ExpiresCache-Control。注意如果同时启用了Cache-Control与ExpiresCache-Control优先级高。
  2. 协商缓存如果强缓存失效后客户端将向服务器发出请求进行协商缓存。浏览器携带上一次请求返回的响应头中的 缓存标识 向服务器发起请求如ETag、Last-Modified等由服务器判断资源是否更新。如果资源没有更新则返回状态码 304 Not Modified告诉浏览器可以使用本地缓存否则返回新的资源内容。强缓存优先级高于协商缓存但是协商缓存可以更加灵活地控制缓存的有效性。

七、页面渲染流程

1. 流程简述

浏览器内核拿到内容后渲染步骤大致可以分为以下几步

1解析HTML: 解析 HTML 并构建 DOM 树。
2解析CSS: 解析 CSS 构建 CSSOM 树样式树。
3合成渲染树DOMCSSOM 合并成一个 渲染树Render Tree 。
4布局计算根据渲染树的结构计算每个节点在屏幕上的大小位置等属性生成布局信息Layout。这个过程会发生回流和重绘。
5绘制页面将生成的布局信息交给浏览器的绘图引擎通过 GPU 加速将像素绘制Paint到屏幕上。
6浏览器回流和重绘如果页面发生改变浏览器需要重新计算布局和绘制这可能会导致性能问题。因此我们应尽量避免频繁的 DOM 操作和调整元素样式以减少不必要的回流和重绘。

2. 解析HTML构建DOM树

浏览器会遵守一套步骤将 HTML 文件转换为 DOM 树。宏观上可以分为几个步骤
在这里插入图片描述
浏览器从磁盘或网络读取HTML的原始字节并根据文件的指定编码例如UTF-8将它们转换成字符串。

在网络中传输的内容其实都是0和1这些字节数据。当浏览器接收到这些字节数据以后它会将这些数据转换为字符串就是我们的代码。

比如假设有这样一个HTML页面以下部分的内容出自参考来源修改了下格式

<html>
  <head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <link href="style.css" rel="stylesheet">
    <title>Critical Path</title>
  </head>
  <body>
    <p>Hello <span>web performance</span> students!</p>
    <div><img src="awesome-photo.jpg"></div>
  </body>
</html>

浏览器的处理过程如下以下图片出自参考来源

在这里插入图片描述

注意点

  1. 将字符串转换成Token例如<html><head><body>等。Token中会标识出当前Token是 “开始标签” 或是 “结束标签” 亦或是 “文本” 等信息。
  2. 构建DOM的过程中不是等所有Token都转换完成后再去生成节点对象而是一边生成Token一边消耗Token来生成节点对象。换句话说每个Token被生成后会立刻消耗这个Token创建出节点对象。注意带有结束标签标识的Token不会再去创建节点对象。

3. 解析CSS构建CSSOM树

构建 CSSOM 树的过程与 构建DOM 的过程非常相似当浏览器接收到一段CSS浏览器首先要做的是识别出Token然后 构建节点 并生成 CSSOM。简述为

在这里插入图片描述

这一过程中CSS匹配HTML元素是一个相当复杂和有性能问题的事情浏览器得递归CSSOM树确定每一个节点的样式到底是什么所以DOM树要小CSS尽量用id和class千万不要过渡层叠下去。

4. 合成渲染树

当我们生成 DOM 树和 CSSOM 树以后就需要将这两棵树组合为 渲染树。
以下图片出自参考来源

在这里插入图片描述
在这一过程中不是简单的将两者合并就行了。渲染树只会包括需要显示的节点和这些节点的样式信息如果某个节点是display: none的那么就不会在渲染树中显示

5. 布局与绘制

当浏览器生成渲染树以后就会根据渲染树来进行布局也可以叫做回流。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”。

布局流程的输出是一个“盒模型”它会精确地捕获每个元素在视口内的确切位置和尺寸所有相对测量值都将转换为屏幕上的绝对像素。

布局完成后浏览器会立即发出“Paint Setup”和“Paint”事件将渲染树转换成屏幕上的像素。

6. 浏览器回流和重绘

  • 回流也叫重排当 DOM结构发生变化 或者 元素样式 发生改变时浏览器需要重新计算样式和渲染树这个过程比较消耗性能。
  • 重绘指元素的外观样式发生变化比如改变 背景色边框颜色文字颜色color等 但是布局没有变此时浏览器只需要应用新样式绘制元素就可以了比回流消耗的性能小一些。

回流必定会发生重绘重绘却可以单独出现 。回流的成本开销要高于重绘而且一个节点的回流往往回导致子节点以及同级节点的回流 所以优化方案中一般都包括尽量避免回流。

什么情况引起回流

  1. 页面渲染初始化
  2. DOM结构改变比如删除了某个节点
  3. render树变化比如减少了padding
  4. 窗口resize
  5. 最复杂的一种获取某些属性引发回流
    很多浏览器会对回流做优化会等到数量足够时做一次批处理回流
    但是除了render树的直接变化当获取一些属性时浏览器为了获得正确的值也会触发回流这样使得浏览器优化无效包括
    (1) offset(Top/Left/Width/Height)
    (2) scroll(Top/Left/Width/Height)
    (3) cilent(Top/Left/Width/Height)
    (4) width,height
    (5) 调用了getComputedStyle()或者IE的currentStyle

所以一般会有一些优化方案如

  1. 避免频繁操作 DOM 和样式。
  2. 使用 DocumentFragment文档片段将多个 DOM 操作合并为一次。
  3. 将需要多次操作的元素缓存起来避免操作时重复访问 DOM。
  4. 对需要改变样式的元素使用 class而不是直接操作 style 属性。
  5. 谨慎使用 table 布局和 offsetLeft/Top 等属性因为它们会强制浏览器进行回流。
  6. 通过修改元素的 display、width、height、position、float、overflow、margin 等属性可以避免触发回流。
  7. 对于频繁读取但不经常改变的页面元素可以使用 CSS 动画实现动态效果避免使用 JS 频繁操作 DOM。

以上几点都是值得注意的细节尤其是对于大型网站或者数据量较大的应用优化回流可以显著提高页面的性能体验。

注意改变字体大小会引发回流。

浏览器渲染小结
整个过程如下

DOM TREEDOMContentLoaded事件触发 => 「执行JS」没完成会阻止接下来的渲染 => CSSOM TREE => RENDER TREE渲染树「浏览器未来是按照这个树来绘制页面的」=> Layout布局计算「回流/重排」=> Painting绘制「重绘」{ 分层绘制 }

需要注意几个事项

1. CSSOM会阻塞渲染只有当CSSOM构建完毕后才会进入下一个阶段构建渲染树。这点与浏览器优化有关防止css规则不断改变避免了重复的构建
2. 通常情况下DOM和CSSOM是并行构建的但是当浏览器遇到一个script标签时DOM构建将暂停直至JS脚本下载完成并执行后才会继续解析HTML。因为 JavaScript 可以使用诸如 document.write() 更改整个 DOM 结构之类的东西来更改文档的形状因此 HTML 解析器必须等待 JavaScript 运行才能恢复HTML文档解析。
3. 如果你想首屏渲染的越快就越不应该在首屏就加载 JS 文件建议将 script 标签放在 body 标签底部。


总结

本文的目的梳理出自己的知识体系。

梳理出知识体系后有了一个大致骨架由于知识点是环环相扣的后期也不容易遗忘。以后就算在这方面又学习了新的知识有了这些基础学起来也会事半功倍些。更重要的是容易举一反三可以由一个普通问题深挖拓展到底层原理。

以后再有相关问题也会继续在这个骨架上填充细节。

可参考
从输入URL到页面加载的过程如何由一道题完善自己的前端知识体系
超详细讲解页面加载过程
浏览器渲染
前端知识体系整理 - 浏览器页面加载过程

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

“浏览器从输入URL到页面渲染加载的过程(浏览器知识体系整理)” 的相关文章