探究浏览器输入URL到加载网页过程中发生的事情

139 阅读12分钟

写在开头

本文首次采用嵌入子文档,一个知识点穿插多个面试要点加追问的文章书写方式。文章嵌入了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)将静态资源分布在多个地理位置,提高访问速度和可靠性。