这是一个经典前端面试题,并且一道题打通了很多知识点的联系。
本文所写的只是这道题的最优通用答法,如果你的面试官针对该过程中的特定部分提出更深入的问题拓展,欢迎在评论区分享你的见解,帮助掘友们打开视野,👏欢迎大家交流和讨论。
全流程如下:
-
URL 解析
-
缓存判断
-
DNS 查询
-
建立 TCP 连接(三次握手)
-
发送 HTTP 请求
-
服务器处理请求并返回 HTTP 响应
-
浏览器解析渲染页面
- 接收到响应后,开始解析 HTML 文件,构建 DOM 树
- 资源加载,如 CSS、Javascript 文件、图片、视频等
- 获取到所有并解析完成后,渲染页面,将结果显示给用户
-
断开 TCP 连接(四次挥手)
一、解析URL
分析 URL 所需要使用的传输协议和请求的资源路径。以下面 URL 为例
https://www.yuque.com/cascading:80/me1zy8/dy4lx3uq85ogtho3?id=110?testID=5&ID=123456&page=1#c3318eaa
一个完整的URL包括以下几部分:
- 协议:HTTP、HTTPS
- 域名:也可以使用 IP 地址作为域名使用,域名就是 IP 地址方便人记忆的写法
- 端口(非必需):省略则使用默认的。HTTP 默认端口是
80
,HTTPS 默认端口是443
- 虚拟目录(非必需):从域名后第一个
"/"
开始到最后一个"/"
为止 - 文件名(非必需):从域名后的最后一个
"/"
开始到"?"
为止,若无问号则到"#"
位置 - 参数(非必需):
"?"
开始到"#"
结束,用于传参和接口查询,多个参数之间用"&"
作为分隔符 - 锚点(非必需):页面定位的地方
二、浏览器缓存判断
浏览器缓存是一种在本地存储网页文件(如 HTML 页面、图片、JavaScript 文件等)的技术,目的是减少加载网页的时间,降低服务器负载,提高用户体验。
- 浏览器首先检查本地缓存中是否有请求的资源。
- 如果有,浏览器会检查资源的有效性。通过
Cache-Control
和Expires
来检查是否命中强缓存,命中则直接从缓存中读取资源,不发起请求。 - 如果没有命中强缓存,此时会暂停缓存查找,等后续 DNS 解析与建立 TCP 连接后,再向服务器发起 HTTP 请求 进行协商缓存。
- 建立连接后,当浏览器第一次发起请求时(请求头中没有
If-Modified-Since
),server 会在响应中告诉浏览器这个资源最后修改的时间(响应头中的Last-Modified
)。 - 当你再次请求这个资源时,浏览器会询问 server 这个资源有没有被修改(请求头中
If-Modified-Since
)。 - 浏览器通过
Etag
和Last-Modify
来与服务器确认返回的响应资源是否被更改,若无更改则返回状态码(304 Not Modified),此时浏览器取本地缓存。
- 建立连接后,当浏览器第一次发起请求时(请求头中没有
- 若协商缓存也没命中,则返回请求的具体数据。
注意:没有命中强缓存的时候,浏览器会暂停缓存的查找 。当通过 DNS 拿到 IP 地址,建立 TCP 连接,再发起 HTTP 请求的时候,会再来校验协商缓存。
强缓存相关字段
Expires
: HTTP/1.0 协议响应头部字段,设置一个绝对时间,告诉浏览器在此之前的都可以直接使用本地缓存,不用发请求。
# Expires 是一个日期和时间
# 这表示该响应将在 2015 年 10 月 21 日的 07:28:00 GMT 后过期。
Expires: Wed, 21 Oct 2015 07:28:00 GMT
Cache-Control
: HTTP/1.1 协议请求和响应头部字段,通过多命令精细化控制缓存行为:比如 max-age:缓存多久(秒)、no-store:不能缓存,用于敏感数据。
协商缓存相关字段
Last-Modify
: HTTP/1.0 协议响应头部字段,包含资源最后被修改的时间,精度到秒。
# 它的值是一个日期和时间
# 这表示该资源最后一次被修改的时间是 1994 年 11 月 15 日的 12:45:26 GMT。
Last-Modified: Tue, 15 Nov 1994 12:45:26 GMT
ETag
: HTTP/1.1 协议响应头部字段,不存时间,而是基于资源内容生成的一段哈希值,比 Last-Modify 更精确,因此若二者同时出现,ETag 优先级更高。
# 这表示该资源的版本标识是 "686897696a7c876b7e"。
# 当资源发生变化时,ETag 也会相应地改变。客户端可以使用 If-None-Match 头提供 ETag 值,以检查资源是否已更改。
# 若资源未更改,服务器将返回状态码 304 Not Modified
# 若资源已更改,服务器将返回新的资源和新的 ETag 值
ETag: "686897696a7c876b7e"
三、DNS解析
DNS(Domain Name System) 是域名和 IP 地址相互映射的一个分布式数据库。
域名分层结构:….三级域名.二级域名.顶级域名
若第二步,没本地缓存或者缓存资源过期,则浏览器接下来执行 DNS 解析动作。浏览器会:
-
先检查浏览器缓存中是否有访问网页的 IP 地址。
-
若浏览器缓存没有则会去
hosts
文件中找。 -
再没有,就去路由器缓存中查询。
-
都没有,浏览器才回去询问本地域名服务器。
-
这个服务器一般是由 ISP 通信运营商提供,不属于树状结构内。若也没有,则它会开启递归查询过程:
- 根域名服务器: 根服务器不存具体域名和IP地址,它存负责特定顶级域的服务器地址。
- 顶级域名服务器: 引导指向查询权威服务器地址。
- 权威域名服务器: 最终提供准确的 IP 地址。
找到之后,本地DNS服务器会把这个域名- IP 地址的映射缓存,供后续使用,并将最终的 IP 地址返回给发起查询的浏览器。
四、TCP 三次握手
当浏览器通过 DNS 解析获取到服务器的 IP 地址后,它会开始建立一个 TCP 连接。
这个过程通常被称为"三次握手",具体步骤如下:
- SYN: 浏览器向服务器发送一个
SYN
包,表示希望建立连接。这个包中包含一个随机的序列号 A。 - SYN-ACK: 服务器收到
SYN
包后,返回一个SYN-ACK
包,表示同意建立连接。这个包中包含一个自己的随机序列号 B,以及对客户端序列号 A 的确认(即 A + 1)。 - ACK: 浏览器收到
SYN-ACK
包后,再发送一个ACK
包,确认已经收到了服务器的SYN-ACK
包。这个包中包含对服务器序列号 B 的确认(即 B + 1)。
五、发送 HTTP 请求
当 TCP 连接建立后,浏览器就可以开始发送 HTTP 请求了。一个 HTTP 请求主要包含以下几个部分:
- 请求行:包含 HTTP方法(如GET、POST等)、请求的 URL 和 HTTP 版本。
- 请求头:包含一些描述请求的元数据,如 User-Agent(描述浏览器类型)、Accept(描述浏览器接受的响应类型)、Connection(如 keep-alive 表示持久连接)等。
- 空行:请求头和请求体之间的一个空行,表示请求头的结束。
- 请求体:对于一些方法(如 POST、PUT 等),请求体中包含了要发送给服务器的数据。
- 对于 GET 方法,请求体通常为空。
典型的 HTTP 请求可能看起来像这样:
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
这个请求使用了 GET 方法,请求的 URL 是 /index.html,HTTP 版本是 1.1。
请求头中包含了一些描述请求的信息,如 Host(服务器的域名)、User-Agent(浏览器类型)等。
POST /api/users HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0
Content-Type: application/json
Accept: application/json
Content-Length: 81
Connection: keep-alive
{
"name": "John Doe",
"email": "john.doe@example.com",
"password": "secret"
}
这个请求使用了 POST 方法,上面的属性基本跟 GET 类似。
新属性 Content-Length 头部表示请求体的长度。空行后是请求体,包含了要发送给服务器的 JSON 数据。当服务器收到这个请求后,会处理请求,然后返回一个 HTTP 响应。
六、服务器处理请求并返回 HTTP 报文
当服务器收到 HTTP 请求后,它会根据请求的内容进行处理,并返回一个 HTTP 响应。一个 HTTP 响应主要包含以下几个部分:
- 状态行:包含 HTTP 版本、状态码和状态文本。状态码是一个三位数,表示请求的处理结果。例如:
- 200 表示请求成功
- 304 表示资源未被改动
- 404 表示请求的资源未找到
- 响应头:包含一些描述响应的元数据,如:
- Content-Type(响应体的类型)
- Content-Length(响应体的长度)
- Set-Cookie(设置cookie)等。
- 空行:响应头和响应体之间的一个空行,表示响应头的结束。
- 响应体:包含服务器返回的数据。这些数据可以是 HTML 文档,也可以是 JSON 数据,或者其他类型的数据。
一个典型的 HTTP 响应可能看起来像这样:
HTTP/1.1 200 OK
Date: Mon, 23 May 2005 22:38:34 GMT
Content-Type: text/html; charset=UTF-8
Content-Encoding: UTF-8
Content-Length: 138
Last-Modified: Wed, 08 Jan 2003 23:11:55 GMT
Server: Apache/1.3.3.7 (Unix) (Red-Hat/Linux)
ETag: "3f80f-1b6-3e1cb03b"
Accept-Ranges: bytes
Connection: close
<html>
<head>
<title>An Example Page</title>
</head>
<body>
Hello World, this is a very simple HTML document.
</body>
</html>
在这个响应中:
- 状态行表示请求成功(200 OK)。
- 响应头中包含了一些描述响应的信息,如 Date(响应的日期和时间)、Content-Type(响应体的类型,这里是text/html)等。
- 响应体中包含了服务器返回的 HTML 文档,这个文档会被浏览器解析并显示给用户。
七、浏览器解析渲染页面
当浏览器接收到服务器的 HTTP 响应后,它会开始解析和渲染页面。这个过程包括以下几个步骤:
- 解析 HTML:浏览器首先解析 HTML 文档,构建 DOM 树。DOM 树是一个表示 HTML 文档结构的树形数据结构。
- 解析 CSS:浏览器解析 CSS 样式信息,构建 CSSOM 树。CSSOM 树是一个表示 CSS 样式信息的树形数据结构。
- 构建渲染树:浏览器将 DOM 树和 CSSOM 树合并,构建渲染树。渲染树表示了要在页面上显示的所有元素及其样式。
- 布局:浏览器计算渲染树中每个节点在页面上的位置,这个过程被称为布局或重排。
- 绘制:浏览器根据渲染树和布局信息,将每个节点绘制到屏幕上,这个过程被称为绘制或重绘。
- 加载 JavaScript:在这个过程中,浏览器还会加载和执行 JavaScript 代码。JavaScript 代码可以修改 DOM 树和 CSSOM 树,从而改变页面的内容和样式。
需要注意的是,这个过程并不是线性的,而是一个循环的过程。
例如,当 JavaScript 代码修改了 DOM 树或 CSSOM 树后,浏览器需要重新构建渲染树,重新进行布局和绘制。这就是所谓的重流和重绘。
八、断开TCP连接
当 HTTP 请求和响应的交互完成后,浏览器和服务器就可以断开 TCP 连接了。
这个过程通常被称为"四次挥手",具体步骤如下:
- FIN: 浏览器发送一个
FIN
(finish)包,表示它已经完成了数据的发送。 - ACK: 服务器收到
FIN
包后,返回一个ACK
(acknowledge)包,确认已经收到了FIN
包。 - FIN: 服务器完成对请求的处理后,也发送一个
FIN
包,表示它已经完成了数据的发送。 - ACK: 浏览器收到
FIN
包后,返回一个ACK
包,确认已经收到了FIN
包。然后浏览器等待一段时间,确保服务器已经收到了ACK
包,然后关闭连接。
这个过程完成后,TCP 连接就被成功关闭了。
需要注意的是,这个过程中的每一个步骤都可能失败,例如,包可能会丢失,或者服务器可能会拒绝关闭连接。如果发生这种情况,浏览器会尝试重新发送 FIN 包,或者报告错误。