写在开头
本文首次采用嵌入子文档,一个知识点穿插多个面试要点加追问的文章书写方式。文章嵌入了TCP的三次握手、常见的http状态码、一种常用的css优化方案、script 标签的 defer 和 async 属性的区别。
文章比较长,大佬们如果感兴趣请耐心读完。希望大佬能对此文章进行评价,在混乱的市场带领小编砥砺前行。
引言
-
简介:网页加载的基本概念
网页加载是用户在浏览器中输入 URL 后,浏览器将该 URL 转换为可以呈现的页面的过程。
-
重要性:理解网页加载过程有助于优化性能和调试
了解每个环节可以帮助理解优化加载速度、提升用户体验,并解决潜在的问题。
用户输入 URL
- 地址栏输入:用户在浏览器地址栏输入URL
- 输入 URL 时,浏览器可能会自动填充历史记录或建议。
- 按下 enter 键后,浏览器开始进行 URL 的解析和处理,触发加载过程。
DNS 解析
- DNS 查询
-
浏览器检查本地缓存
浏览器首先会检查本地 DNS 缓存,查看是否已存在对应的 IP 地址
-
如果没有,发送 DNS 请求
如果没有找到,浏览器会向配置的 DNS 服务器发动查询请求。
配置的 DNS 服务器通常是 操作系统配置、DHCP(动态主机配置协议)、无线或者有线,会使用当前的网络配置
-
DNS 服务器返回 IP 地址
- DNS 服务器接收到请求后,会进行域名解析,并将对应的 IP 地址返回给浏览器。
- 此过程可能涉及多个 DNS 服务器的查询。
-
建立TCP连接
-
TCP 三次握手
-
客户端与服务器之间建立连接
浏览器与服务器之间通过 TCP 进行连接,确保数据可靠传输。
-
确认双方的接收能力
- 客户端发送 SYN:浏览器向服务器发送连接请求。
- 服务器回应 SYN-ACK:服务器确认请求,并返回确认。
- 客户端发送 ACK:浏览器再次确认,连接建立成功。
-
穿插聊一聊什么是
TCP的三次握手TCP 连接的三次握手是建立可靠连接的过程,确保客户端和服务器之间可以顺利通信。 ### 三次握手的步骤 #### 第一次握手(SYN) - 客户端发送一个 SYN (Synchronize)包到服务器,表示请求建立连接。 - 这个包中包含客户端的初始序列号(ISN),用户后续的数据传输。 示例 客户端 send => `SYN, Seq=100` #### 第二次握手(SYN-ACK) - 服务器收到 SYN 包之后,回复一个 SYN-ACK 包 - 这个包包含服务器的初始序列号,并确认客户端的序列号(Ack = 客户端序列号 + 1) 示例 服务器 send => `SYN-ACK, Seq=200, Ack=101` #### 第三次握手 (ACK) - 客户端收到 SYN-ACK 包后,发送一个 ACK(Acknowledgment)包给服务器,确认收到服务器的 SYN-ACK。 - 此 ACK 包的确认号为服务器的序列号加1。 示例 客户端 send => `ACK, Seq=101,ACK=201` ### 握手成功后 三次握手完成后,客户端和服务器之间的 TCP 连接就建立成功了,双方可以开始数据传输。 ### 注意事项 - 三次握手不仅用于连接的建立,还可以防止旧的连接请求影响新连接。 - 如果在任何一步中没有收到应答,握手将失败,客户端会重试或放弃连接。 ### 握手失败的情况 - 超时 如果客户端在发送 SYN 后未在规定时间内收到 SYN-ACK,则认为握手失败,可能是网络问题或服务器未响应。 - 拒绝连接 服务器收到SYN请求,但由于负载过高、配置错误或其他问题,发送 RST 包回复客户端,表示拒绝连接。 - 网络问题 包丢失或路由问题可能导致 SYN 或 SYN-ACK 包无法到达对方,这会使握手中断。 - 无效的响应 如果客户端收到的包不是预期的 SYN-ACK (例如:收到的数据包格式不正确或序列号不匹配),则握手也会失败。 ### 问题来了,是每次都需要进行三次握手吗? > 不管是 GET、POST、PUT、DELETE 等 http 方法,TCP 连接的三次握手是建立连接的必要步骤,但不是每次请求都需要进行三次握手 #### 持久连接(Keep-Alive) - 建立连接 当客户端首次发送请求(如 GET、POST等)时,会进行三次握手以建立 TCP 连接。 - 使用同一连接 如果TCP使用持久连接,客户端可以在同一个 TCP 连接上发送多个请求。这样,后续的请求不需要再进行三次握手,只需要在已有连接上发送数据。 - 关闭连接 在客户端或服务器完成所有请求后,可以选择关闭连接,这时会发送一个 FIN 包,表示连接结束。 #### 非持久连接 如果不使用持久连接,每个请求都需要建立的新的 TCP 连接,这意味着每个请求都会经历三次握手。这在负载较高的场景下会导致额外的延迟和资源消耗。 #### 请求方法与握手的关系 - GET:通常用来请求数据,若使用持久连接,则只在首次请求时进行三次握手。 - POST:用于提交数据,首次请求时需要握手,后续请求可在同一连接上进行。 - PUT、DELETE:同样,首次请求时需要握手,后续请求可复用连接。 > 使用持久化连接可以有效减少延迟,提高性能,因此现代的 HTTP/1.1 默认启用 Keep-Alive ### 问题又来了,怎么样可以称之为"同一连接"呢? "同一连接"指的是在同一个 TCP 连接上进行的多个请求和响应。尽管这些请求可能指向不同的资源,但只要它们在同一个TCP连接中进行,便称之为"同一连接"。 #### TCP 连接与请求的关系 - TCP 连接的特性 - TCP 连接是通过三次握手建立的,双方在连接建立后可以进行多次数据传输。 - 每个 TCP 连接通过 `源IP、源端口、目标IP和目标端口`四个参数唯一标识。 - HTTP 请求 - 即使不同的HTTP请求指向不同的URL(如`qqq.con/zbb`和`qqq.com/xbb`),如果他们是在同一个 TCP 连接上进行的,仍然可以视为"同一连接" #### 使用持久连接的好处 - 减少延迟:在同一 TCP 连接上发送多个请求可以减少每个请求的建立和拆除连接的时间。 - 资源节省:避免了频繁的握手和挥手的过程,节省了系统资源。
-
发送 HTTP 请求
-
构建 HTTP 请求
-
请求方法(GET/POST等)
-
请求头和请求体的准备
包含必要的信息,如用户代理、内容类型等
- 例如:auth,携带token给到服务端,进行身份验证
-
-
发送请求
-
浏览器通过 TCP 连接发送 HTTP 请求
浏览器通过已建立的 TCP 连接发送 HTTP 请求至服务器。
-
服务器处理请求
-
接受请求
- 服务器接收到 HTTP 请求
-
处理请求
-
服务器进行业务逻辑处理
根据请求的类型和内容,服务器执行相关的逻辑。
-
数据库查询(如果需要)
如果有必要,服务器会与数据库交互以获取请求数据。
-
-
生成响应
-
服务器生成 HTTP 响应
服务器根据处理结果生成 HTTP 响应,包括状态码、响应头和响应体。
-
接收 HTTP 响应
- 返回响应
- 服务器通过 TCP 连接返回 HTTP 响应
- 响应头和响应体的内容
浏览器处理响应
-
解析响应
-
检查状态码(200, 404 等)
浏览器检查 HTTP 状态码以判断请求是否成功(如200表示成功,404表示未找到)
-
处理重定向(如 301/302等)
如状态码为301 或者 302,浏览器会自动处理重定向,可能重新请求新的 URL。
### 关联一道面试题,常见的HTTP状态码 #### 2xx 系列:成功 - 200 OK:请求成功,服务器返回所请求的资源。 - 201 Created:请求成功,并在服务器上创建了新资源。 - 204 No Content:请求成功,但没有返回内容。 #### 3XX系列:重定向 - 301 Moved Permanently:请求的资源已永久移动到新位置。 - 302 Found:请求的资源临时移动到另一个位置,客户端应继续使用原来的URL。 - 304 Not Modified:资源未修改,客户端可以使用缓存的版本。 #### 4XX系列: 客户端错误 - 400 Bad Request:请求无效,服务器无法理解。 - 401 Unauthorized:请求未授权,需提供身份验证。 - 403 Forbidden:服务器拒绝请求,用户没有权限访问。 - 404 Not Found:请求的资源未找到,常见的404错误页面。 #### 500系列:服务器错误 - 500 Internal Server Error:服务器内部错误,无法完成请求,通常是代码或配置的问题。 - 502 Bad Gateway:服务器作为网关或代理时,从上游服务器收到无效响应。 - 503 Service Unavailable:服务器当前无法处理请求,可能是过载或维护。
-
-
渲染页面
-
解析 HTML 页面
-
构建 DOM 树
浏览器开始解析 HTML,构建 DOM 树。
-
处理 CSS (构建CSSOM树)
解析 CSS 文件,构建 CSSOM 树以计算样式。
CSS 树的构建通常是从DOM树(从上到下)和样式表(从下到上,也叫从右往左)结合的过程。 这里介绍一种常用的优化方案 `a-b__c--d` 类命名策略,被称为 BEM(Block Element Modifier)方法论。 ### BEM 旨在提高代码的可读性、可维护性和重用性。 #### 概念 - Block(块):表示独立的页面组件,具有独特的功能和意义。 - 示例: `.button` 表示一个按钮块 - Element(元素):块的组成部分,无法单独存在,通常用双下划线连接块和元素名称 - 示例: `.button__icon` 表示按钮中的图标元素。 - Modifier(修饰符):表示块或元素的不同状态、风格,通常用双短横线连接 - `.button--primary` 表示主要样式的按钮 #### 命名规则 - 块: 命名时选择一个简洁且具有描述性的名称。 - 例: `.nav` 、`.card` - 元素: 在块名称后添加双下划线和元素名称 - 例: `.nav__item` 、`.card__title` - 修饰符: 在块或元素名称后添加双短横线和修饰符名称 - 例: `.nav--active` 、`.card_title--high` #### 优势 - 模块化: BEM 使得组件独立于上下文,便于重用和维护。 - 可读性: 清晰的命名规则让代码更容易理解,减少了团队协作中的误解 - 避免冲突: 独特的命名方式减少了 CSS 选择器的冲突,提高了可维护性。 #### 注意事项 - 建议不要使用过深的嵌套,保持结构简洁。 - 尽量使用简单易懂的命名,避免冗长和复杂的名称。 #### 示例 ```html <div class="button button--primary"> <span class="button__icon">¤</span> <span class="button__text">Search</span> </div> ``` -
生成渲染树
结合 DOM 和 CSSOM 生成渲染树,决定哪些节点需要显示。
-
执行 JavaScrip (DOM 操作和样式计算)
如果遇到
<script>标签,浏览器会执行 JavaScript 代码,可能会修改 DOM 或样式。业务 script 代码一定要写在body的最后,不要问为什么!
### 这里也可以穿插一道面试题,`<script>` 标签中 defer 和 async 的区别 首先,如果没有 async 或者 defer,浏览器会立即加载并执行相应的脚本。他不会等待后续加载的文档土元素,读取到就会开始加载和执行,会阻塞后续文档的加载。 #### 相同点 - async 和 defer 属性都是异步去加载外部的 js 脚本文件,他们不会阻塞页面的解析。 #### 不同点 - 执行顺序的问题 - 多个带 async 属性的标签,不能保证加载的顺序; - 多个带 defer 属性的标签,按照加载顺序执行。 - 脚本是否并行执行 - async 属性,表示后续文档的加载和 js 脚本的加载是并行的,即异步执行,但是,一旦脚本下载完成,它会立即执行,不管文档是否已经完全解析。 - defer 属性,表示后续文档的加载和 js 脚本的加载(仅加载不执行)是并行的,即异步执行,但是,js 脚本需要等到文档全部解析完成之后才执行,DOMContentLoaded 事件触发执行之前。 #### 所以建议总是把 js 脚本放在最后执行。 ### 追问,script 标签如果同时存在 async 和 defer 标签,会有什么问题 当 `<script>` 标签同时存在 async 和 defer 属性时,浏览器会忽略 defer 而仅按照 async 的方式来加载和执行脚本。 因为两个属性的冲突,文档解析过程中,async 会优先于 defer。 所以两个属性同时存在时,浏览器会以 async 的方式来加载脚本,这可能会影响页面的行为和性能,也可能会产生不可预测的行为和潜在的问题。 一定要避免两个属性同时存在。
-
页面加载优化
- 懒加载与预加载
- 懒加载:仅在需要时加载资源(如图像)。
- 预加载:提前加载可能需要的资源,以减少加载时间。
- 使用缓存
- 浏览器利用缓存(如 HTTP 缓存)来减少请求次数,提高加载速度。
- CDN 加速
- 使用内容分发网络(CDN)将静态资源分布在多个地理位置,提高访问速度和可靠性。