前言
- 面试常被问到:浏览器从URL输入到页面展现到底发生了什么?这也是一个前端面试经典问题了,本文就带你彻底了解面试官到底问这个问题想让你回答些什么。
过程概览
输入URL地址 --> 浏览器缓存 --> DNS域名解析 --> TCP三次握手 --> 发送HTTP请求
--> HTTP请求响应 --> 浏览器解析渲染页面 --> TCP四次挥手
什么是 URL
URL(Uniform Resource Locator),统一资源定位符,用于定位互联网上资源,俗称网址。遵守语法规则:scheme://host.domain:port/path/filename-
scheme- 因特网服务的类型。常见的协议有http、https、ftp、file,其中最常见的类型是http,而https则是进行加密的网络传输。 -
host- 域主机(http的默认主机是www)。 -
domain- 因特网域名,比如juejin.cn。 -
port- 主机上的端口号(默认端口号:http/80、https/443)。 -
path- 服务器上的路径(如果省略,则文档必须位于网站的根目录中)。 -
filename- 文档/资源的名称。
-
浏览器缓存
- 通过判断浏览器是否有对应
URL的缓存; - 根据是否需要重新向服务器发起请求来分类,浏览器缓存分为:
-
强缓存(如果资源存在且有效直接读取本地磁盘缓存,无需建立连接)。
-
协商缓存(服务端根据
Last-modified + etag判断数据是否更新,若更新则返回200+ 新数据,否则返回304, 读取本地资源)。
-
强缓存
-
Expires(设置过期时间)Expires是HTTP/1.0的产物,表示缓存资源到期的时间,如果系统的时间小于该时间,则不会发送请求。由于系统的时间是可以修改的,所以修改了时间的话不一定会满足预期。
-
Cache-ControlCache-Control是HTTP/1.1新增的字段,主要用于控制网页缓存。包含以下可设置值:-
public: 响应内容会被客户端和代理服务器缓存。 -
private:响应内容只能被客户端缓存。 -
max-age:表示缓存的响应内容的有效期,即在xx秒后失效。 -
s-maxage:表示缓存在代理服务器中的响应内容在xxx秒后失效,s-maxage的优先级比max-age高。 -
no-store: 表示服务器响应的内容不使用缓存。 -
no-cache(该指令用于协商缓存):表示客户端在从缓存取数据之前,需要会先向服务器发送请求验证当前缓存内容是否有更新,如果有,服务器会返回新的响应报文。
-
-
Expires和Cache-Control对比Expires是http1.0的产物,Cache-Control是http1.1的产物。Expires其实是过时的产物,现阶段它的存在只是一种兼容性的写法。- 两者同时存在的话,
Cache-Control优先级高于Expires。
协商缓存:
- 客户端再次请求资源时时,会向服务器发送请求验证当前资源的有效性。
-
Last-Modified(根据文件修改时间来决定是否从缓存取数据)-
浏览器在第一次访问资源时,服务器返回资源的同时,在
response header中添加Last-Modified字段,值是这个资源在服务器上的最后修改时间,浏览器接收后会缓存文件和header。 -
由于存在
no-cache指令,所以浏览器会向服务器发送请求验证缓存的内容是否有更新,请求的报文中会添加一个if-Modified-Since字段,值就是之前缓存标识中的Last-Modified。 -
服务器收到这个请求时,会将
If-Modified-Since中的值与服务器中这个资源的最后修改的时间进行对比。 -
如果没有变化,返回状态码为
304的空的报文,然后客户端是直接从缓存读取数据。 -
如果
If-Modified-Since的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是就返回状态码为200的新资源。 -
最后浏览器再将新的响应报文和对应的缓存标识缓存起来。
-
-
Etag方法(根据文件内容是否修改来决定是否从缓存取数据)-
Etag字段是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag就会重新生成。 -
具体过程与
Last-Modified类似,只是时间的比较变成了资源唯一标识符的比较。 -
服务器在第一次响应的报文中会添加一个
Etag字段,存储的是当前资源文件的唯一标识。 -
浏览器在下一次发送请求时会在请求头中添加
If-None-Match字段,取值就是之前的Etag值。 -
服务器在接收到请求后会将该字段的值与当前资源文件的
ETag进行比较。 -
若相同,则表示资源没有更新,会返回状态码为
304的空报文。表示浏览器从本地缓存中取数据。 -
若不同,则会返回状态码为
200的新资源。
-
-
Last-Modified与Etag比较
-
精确度上,
Etag要优于Last-Modified。采用Last-Modified方法时,如果服务器端在不可感知的时间里修改了文件,Last-Modified其实并没有体现出来修改,此时客户端获取到的仍是旧数据。 -
性能上,
Etag要逊于Last-Modified,Last-Modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值。 -
优先级上,服务器校验优先考虑
Etag。
缓存机制
-
强缓存优先于协商缓存进行即先判断是否命中强缓存再判断是否走协商缓存。
-
强缓存(
Expires和Cache-Control)生效则直接使用缓存,不再走协商缓存路线。 -
Cache-Control值不为no-store并且未命中强缓存则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match)。- 协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么该请求的缓存失效了,返回
200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回304,继续使用缓存。
- 协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么该请求的缓存失效了,返回
DNS 域名解析
-
未缓存,通过
URL我们是不知道要访问哪一个服务器的,所以我们需要通过DNS域名解析拿到对应的IP地址(互联网协议地址,是IP Address的缩写)。 -
DNS解析大致过程:-
浏览器先检查自身缓存中有没有被解析过的这个域名对应的
IP地址(在chrome浏览器地址栏中输入chrome://appcache-internals/可以看见chrome的本地缓存地址)。 -
浏览器缓存中没有命中,浏览器会检查操作系统缓存中有没有对应的已解析过的结果。而操作系统也有一个域名解析的过程。(在
Windows中可通过C盘里一个叫hosts的文件来设置,如果你在这里指定了一个域名对应的IP地址,那浏览器会首先使用这个IP地址。即使解析失败,也不会继续进行后面的步骤。) -
至此还没有命中域名,会请求本地域名服务器(
LDNS)来解析这个域名,这台服务器一般在你的城市的某个角落,距离你不会很远,并且这台服务器的性能都很好,一般都会缓存域名解析结果,大约80%的域名解析到这里就会完成。 -
LDNS仍然没有命中,就直接跳到Root Server域名服务器请求解析。 -
根域名服务器返回给 LDNS 一个所查询域的主域名服务器(
gTLD Server,国际顶尖域名服务器,如.com.cn.org等)地址。 -
此时
LDNS再发送请求给上一步返回的gTLD Server。 -
接受请求的
gTLD Server查找并返回这个域名对应的Name Server的地址,这个Name Server就是网站注册的域名服务器。 -
Name Server根据映射关系表找到目标IP,返回给LDNS。 -
LDNS缓存这个域名和对应的IP。 -
LDNS把解析的结果返回给用户,用户根据TTL值缓存到本地系统缓存中,域名解析过程至此结束。
-
TCP 三次握手建立连接
- 目的:为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。
- 过程如下:
- 第一次握手:客户端发送一个带
SYN=1,Seq=X的数据包到服务器端口。 - 第二次握手:服务器发回一个带
SYN=1, ACK=X+1, Seq=Y的响应包以示传达确认信息。 - 第三次握手:客户端再回传一个带
ACK=Y+1, Seq=Z的数据包,代表“握手结束” 。
- 第一次握手:客户端发送一个带
发送 HTTP 请求
- 一个
HTTP请求报文由请求行(request line)、请求头部(headers)、空行(blank line)和请求数据(request body)4个部分组成。
- 请求行包含请求方法、
URL、协议版本- 请求方法包含
8种:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE。HTTP 请求方法 - URL 即请求地址,由 <协议>://<主机>:<端口>/<路径>?<参数> 组成。
- 协议版本即
http版本号。主版本号.次版本号,常用的有HTTP/1.0、HTTP/1.1和HTTP/2.0。
POST /detanx HTTP/1.1 // 请求行 - 请求方法包含
- 请求头
- 请求头包含请求的附加信息,由关键字/值对组成,每行一对,关键字和值用英文冒号
“:”分隔。 - 请求头/响应头参照表
- 请求头包含请求的附加信息,由关键字/值对组成,每行一对,关键字和值用英文冒号
- 请求数据
- 可以承载多个请求参数的数据,包含回车符、换行符和请求数据,并不是所有请求都具有请求数据,例如某些
get请求(获取所有国家列表等)。
- 可以承载多个请求参数的数据,包含回车符、换行符和请求数据,并不是所有请求都具有请求数据,例如某些
POST /detanx HTTP/1.1 // 请求行
// 请求头
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:10.0.2) Gecko/20100101 Firefox/10.0.2
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8
Accept-Language: zh-cn,zh;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Referer: http://localhost/
...
// ---- 空行 ----
username=detanx&password=Aa123456 // 请求数据
HTTP 响应请求
HTTP响应报文由状态行(status line)、相应头部(headers)、空行(blank line)和响应数据(response body)4个部分组成。
- 状态行
- 状态行由
3部分组成,分别为:协议版本、状态码、状态码扫描。其中协议版本与请求报文一致,状态码描述是对状态的简单描述。状态码具体含义
- 状态行由
- 响应头
- 响应头与请求头一样,只是与请求头包含的附加信息有所不同。
- 请求头/响应头参照表
- 响应数据
- 根据请求类型的不同,响应的数据格式也有所不同,有可能是二进制文件流、
JSON对象、字符串、HTML文件等。
- 根据请求类型的不同,响应的数据格式也有所不同,有可能是二进制文件流、
HTTP/1.1 200 OK // 状态行
// 响应头
Date: Sun, 17 Mar 2013 08:12:54 GMT
Set-Cookie: PHPSESSID=c0huq7pdkmm5gg6osoe3mgjmm3; path=/
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 4393
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: application/json
...
// ---- 空行 ----
{name: 'detanx'} // 响应数据
浏览器解析渲染页面
- 浏览器解析渲染页面分为一下五个步骤:
- 根据
HTML解析出DOM树 - 根据
CSS解析生成CSS规则树 CSS规则树附着到DOM树上 ,构造生成渲染(Render)树- 根据渲染树计算每一个节点的信息
- 根据计算好的信息绘制页面
- 根据
根据 HTML 解析出 DOM 树
- 读取
HTML文档,构建DOM树的过程中,遇到外联的样式声明或脚本声明,如果未设置defer或async属性(见下文相关补充),则暂停文档解析,创建新的网络连接,开始下载样式文件和脚本文件。 - 根据
HTML的内容,将标签按照结构解析成为DOM树,DOM树解析的过程是一个深度优先遍历。即先构建当前节点的所有子节点,再构建下一个兄弟节点。
根据 CSS 解析生成 CSS 规则树
- 当读取到
CSS相关的文件时,就会触发 CSS 规则树的解析。 - 直到
CSS规则树和DOM树都解析完成前,浏览器都不会进行渲染。
CSS 规则树附着到 DOM 树上 ,构造生成渲染(Render)树
- 当
CSS规则树和DOM树都解析完成后,就会把CSS规则树附着到DOM树上构造出浏览器需要渲染的Render树。 - 如果
DOM树先于CSS规则树构建完成,则在CSS规则树构建完成后,页面会发生一次重绘,将新构建的CSS规则应用于渲染树。
根据渲染树计算每一个节点的信息
- 布局:通过渲染树中渲染对象的信息,解析
position、overflow、z-index、display等等属性,计算每一个渲染树节点的位置和大小。 - 回流:布局完成后,发现了某个部分发生了变化影响了布局,那就需要倒回去重新渲染。最后调用操作系统的
Native GUI API完成绘制(repain)。 - 渲染树的节点:
Gecko中称为frame,而在webkit中称为renderer.
根据计算好的信息绘制页面
- 绘制阶段,系统会遍历呈现树,并调用呈现器的 “
paint”方法,将呈现器的内容显示在屏幕上。
script 标签 defer 和 async 属性补充
defer:开启新的线程下载脚本文件,并使脚本在文档解析完成后执行。defer只适用于外联脚本,如果script标签没有指定src属性,只是内联脚本,不要使用defer。- 多个声明
defer的脚本,则会按顺序下载和执行。 defer脚本会在DOMContentLoaded和load事件之前执行。
async(HTML5新增属性):异步下载脚本文件,下载完毕立即解释执行代码。- 只适用于外联脚本,这一点和
defer一致。 - 多个声明
async的脚本,不能确保彼此的先后顺序,其下载和执行都是异步的。 async会在load事件之前执行,但并不能确保与DOMContentLoaded的执行先后顺序。
- 只适用于外联脚本,这一点和
- 注:
DOMContentLoaded事件是当初始HTML文档完全被加载和解析(即所有的DOM完全解析)时触发的,无需要等待样式表,图片,子框架完成加载。而onload事件要等页面所有元素,包括图片以及脚本等全部加载完成才触发,因此它比DOMContentLoaded要更晚执行。
回流和重绘
- 回流
- 当
Render Tree中的一部分(或全部)因为元素的几何属性、规模尺寸、布局、隐藏等改变而需要重新构建。每个页面至少需要一次回流,就是在页面第一次加载的时候,这时候是一定会发生回流的,因为要构建Render Tree。
- 当
- 重绘
- 当
Render Tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如background-color、color等。
- 当
- 回流必将引起重绘,而重绘不一定会引起回流。
TCP 四次挥手断开连接
- 过程如下:(主动方 --> 浏览器,被动方 --> 服务器)
- 第一次挥手:主动方向被动方发送报文,
Fin、Ack、Seq,表示已经没有数据传输了。并进入FIN_WAIT_1状态。 - 第二次挥手:被动方发送报文,
Ack、Seq,表示同意关闭请求。此时主机主动方进入FIN_WAIT_2状态。 - 第三次挥手:被动方向主动方发送报文段,
Fin、Ack、Seq,请求关闭连接。并进入LAST_ACK状态。 - 第四次挥手:主动方向被动方发送报文段,
Ack、Seq。然后进入等待TIME_WAIT状态。被动方收到主动方的报文段以后关闭连接。主动方等待一定时间未收到回复,则正常关闭。
- 第一次挥手:主动方向被动方发送报文,
参考链接
往期精彩
- 前端开发基本规范
- 从0搭建Vite + Vue3 + Element-Plus + Vue-Router + ESLint + husky + lint-staged
- 「前端进阶」JavaScript手写方法/使用技巧自查
- JavaScript设计模式之简介及创建型模式
- 公众号打开小程序最佳解决方案(Vue)
- Axios你可能不知道使用方式
「点赞、收藏和评论」
❤️关注+点赞+收藏+评论+转发❤️,创作不易,鼓励笔者创作更好的文章,谢谢🙏大家。