前言
- 面试常被问到:浏览器从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-Control
Cache-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你可能不知道使用方式
「点赞、收藏和评论」
❤️关注+点赞+收藏+评论+转发❤️,创作不易,鼓励笔者创作更好的文章,谢谢🙏大家。