彻底搞懂:浏览器从URL输入到页面展现到底发生了什么?

2,196 阅读14分钟

前言

  • 面试常被问到:浏览器从URL输入到页面展现到底发生了什么?这也是一个前端面试经典问题了,本文就带你彻底了解面试官到底问这个问题想让你回答些什么。

过程概览

输入URL地址 --> 浏览器缓存 --> DNS域名解析 --> TCP三次握手 --> 发送HTTP请求 
--> HTTP请求响应 --> 浏览器解析渲染页面 --> TCP四次挥手

什么是 URL

  • URL(Uniform Resource Locator),统一资源定位符,用于定位互联网上资源,俗称网址。遵守语法规则:scheme://host.domain:port/path/filename
    1. scheme - 因特网服务的类型。常见的协议有 httphttpsftpfile,其中最常见的类型是 http,而 https 则是进行加密的网络传输。

    2. host - 域主机(http 的默认主机是 www)。

    3. domain - 因特网域名,比如 juejin.cn

    4. port - 主机上的端口号(默认端口号:http/80https/443)。

    5. path - 服务器上的路径(如果省略,则文档必须位于网站的根目录中)。

    6. filename - 文档/资源的名称。

浏览器缓存

  • 通过判断浏览器是否有对应 URL 的缓存;
  • 根据是否需要重新向服务器发起请求来分类,浏览器缓存分为:
    • 强缓存(如果资源存在且有效直接读取本地磁盘缓存,无需建立连接)。

    • 协商缓存(服务端根据 Last-modified + etag 判断数据是否更新,若更新则返回 200 + 新数据,否则返回 304, 读取本地资源)。

强缓存

  1. Expires(设置过期时间)

    • ExpiresHTTP/1.0 的产物,表示缓存资源到期的时间,如果系统的时间小于该时间,则不会发送请求。由于系统的时间是可以修改的,所以修改了时间的话不一定会满足预期。
  2. Cache-Control

    • Cache-ControlHTTP/1.1 新增的字段,主要用于控制网页缓存。包含以下可设置值:
      • public: 响应内容会被客户端和代理服务器缓存。

      • private:响应内容只能被客户端缓存。

      • max-age:表示缓存的响应内容的有效期,即在 xx 秒后失效。

      • s-maxage:表示缓存在代理服务器中的响应内容在 xxx 秒后失效,s-maxage 的优先级比 max-age

      • no-store: 表示服务器响应的内容不使用缓存。

      • no-cache(该指令用于协商缓存):表示客户端在从缓存取数据之前,需要会先向服务器发送请求验证当前缓存内容是否有更新,如果有,服务器会返回新的响应报文。

  3. ExpiresCache-Control 对比

    • Expireshttp1.0 的产物,Cache-Controlhttp1.1 的产物。Expires 其实是过时的产物,现阶段它的存在只是一种兼容性的写法。
    • 两者同时存在的话,Cache-Control 优先级高于 Expires

协商缓存:

  • 客户端再次请求资源时时,会向服务器发送请求验证当前资源的有效性。
  1. Last-Modified(根据文件修改时间来决定是否从缓存取数据)

    • 浏览器在第一次访问资源时,服务器返回资源的同时,在 response header 中添加 Last-Modified 字段,值是这个资源在服务器上的最后修改时间,浏览器接收后会缓存文件和 header

    • 由于存在 no-cache 指令,所以浏览器会向服务器发送请求验证缓存的内容是否有更新,请求的报文中会添加一个 if-Modified-Since 字段,值就是之前缓存标识中的Last-Modified

    • 服务器收到这个请求时,会将 If-Modified-Since 中的值与服务器中这个资源的最后修改的时间进行对比。

    • 如果没有变化,返回状态码为 304 的空的报文,然后客户端是直接从缓存读取数据。

    • 如果 If-Modified-Since 的时间小于服务器中这个资源的最后修改时间,说明文件有更新,于是就返回状态码为 200 的新资源。

    • 最后浏览器再将新的响应报文和对应的缓存标识缓存起来。

  2. Etag 方法(根据文件内容是否修改来决定是否从缓存取数据)

    • Etag字段是服务器响应请求时,返回当前资源文件的一个唯一标识(由服务器生成),只要资源有变化,Etag 就会重新生成。

    • 具体过程与 Last-Modified 类似,只是时间的比较变成了资源唯一标识符的比较。

    • 服务器在第一次响应的报文中会添加一个 Etag 字段,存储的是当前资源文件的唯一标识。

    • 浏览器在下一次发送请求时会在请求头中添加 If-None-Match 字段,取值就是之前的Etag值。

    • 服务器在接收到请求后会将该字段的值与当前资源文件的 ETag 进行比较。

    • 若相同,则表示资源没有更新,会返回状态码为 304 的空报文。表示浏览器从本地缓存中取数据。

    • 若不同,则会返回状态码为 200 的新资源。

  3. Last-ModifiedEtag 比较

  • 精确度上,Etag要优于Last-Modified。采用 Last-Modified 方法时,如果服务器端在不可感知的时间里修改了文件,Last-Modified 其实并没有体现出来修改,此时客户端获取到的仍是旧数据。

  • 性能上,Etag 要逊于 Last-ModifiedLast-Modified只需要记录时间,而 Etag 需要服务器通过算法来计算出一个 hash值。

  • 优先级上,服务器校验优先考虑 Etag

缓存机制

  • 强缓存优先于协商缓存进行即先判断是否命中强缓存再判断是否走协商缓存。

  • 强缓存(ExpiresCache-Control)生效则直接使用缓存,不再走协商缓存路线。

  • Cache-Control 值不为 no-store 并且未命中强缓存则进行协商缓存(Last-Modified / If-Modified-SinceEtag / If-None-Match)。

    • 协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么该请求的缓存失效了,返回 200,重新返回资源和缓存标识,再存入浏览器缓存中;生效则返回 304,继续使用缓存。

DNS 域名解析

  • 未缓存,通过 URL 我们是不知道要访问哪一个服务器的,所以我们需要通过 DNS 域名解析拿到对应的 IP 地址(互联网协议地址,是 IP Address 的缩写)。

  • DNS 解析大致过程: image.png

    1. 浏览器先检查自身缓存中有没有被解析过的这个域名对应的 IP 地址(在 chrome 浏览器地址栏中输入 chrome://appcache-internals/ 可以看见 chrome 的本地缓存地址)。 image.png

    2. 浏览器缓存中没有命中,浏览器会检查操作系统缓存中有没有对应的已解析过的结果。而操作系统也有一个域名解析的过程。(在 Windows 中可通过 C 盘里一个叫 hosts 的文件来设置,如果你在这里指定了一个域名对应的 IP 地址,那浏览器会首先使用这个 IP 地址。即使解析失败,也不会继续进行后面的步骤。)

    3. 至此还没有命中域名,会请求本地域名服务器(LDNS)来解析这个域名,这台服务器一般在你的城市的某个角落,距离你不会很远,并且这台服务器的性能都很好,一般都会缓存域名解析结果,大约 80% 的域名解析到这里就会完成。

    4. LDNS 仍然没有命中,就直接跳到 Root Server 域名服务器请求解析。

    5. 根域名服务器返回给 LDNS 一个所查询域的主域名服务器(gTLD Server,国际顶尖域名服务器,如.com .cn .org 等)地址。

    6. 此时 LDNS 再发送请求给上一步返回的 gTLD Server

    7. 接受请求的 gTLD Server 查找并返回这个域名对应的 Name Server 的地址,这个 Name Server 就是网站注册的域名服务器。

    8. Name Server 根据映射关系表找到目标 IP,返回给 LDNS

    9. LDNS 缓存这个域名和对应的 IP

    10. LDNS 把解析的结果返回给用户,用户根据 TTL 值缓存到本地系统缓存中,域名解析过程至此结束。

TCP 三次握手建立连接

  • 目的:为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误image.png
  • 过程如下:
    • 第一次握手:客户端发送一个带 SYN=1,Seq=X 的数据包到服务器端口
    • 第二次握手:服务器发回一个带 SYN=1, ACK=X+1, Seq=Y 的响应包以示传达确认信息
    • 第三次握手:客户端再回传一个带 ACK=Y+1, Seq=Z 的数据包,代表“握手结束”

发送 HTTP 请求

  • 一个 HTTP 请求报文由请求行(request line)、请求头部(headers)、空行(blank line)和请求数据(request body4 个部分组成。 image.png
  1. 请求行包含请求方法、URL、协议版本
    • 请求方法包含 8 种:GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACEHTTP 请求方法 image.png
    • URL 即请求地址,由 <协议>://<主机>:<端口>/<路径>?<参数> 组成。
    • 协议版本即 http 版本号。主版本号.次版本号,常用的有 HTTP/1.0HTTP/1.1HTTP/2.0
    POST /detanx HTTP/1.1 // 请求行
    
  2. 请求头
    • 请求头包含请求的附加信息,由关键字/值对组成,每行一对,关键字和值用英文冒号“:”分隔。
    • 请求头/响应头参照表
  3. 请求数据
    • 可以承载多个请求参数的数据,包含回车符、换行符和请求数据,并不是所有请求都具有请求数据,例如某些 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 body4 个部分组成。 image.png
  1. 状态行
    • 状态行由 3 部分组成,分别为:协议版本、状态码、状态码扫描。其中协议版本与请求报文一致,状态码描述是对状态的简单描述。状态码具体含义 image.png
  2. 响应头
  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'} // 响应数据

浏览器解析渲染页面

image.png

  • 浏览器解析渲染页面分为一下五个步骤:
    • 根据 HTML 解析出 DOM
    • 根据 CSS 解析生成 CSS 规则树
    • CSS 规则树附着到 DOM 树上 ,构造生成渲染(Render)树
    • 根据渲染树计算每一个节点的信息
    • 根据计算好的信息绘制页面

根据 HTML 解析出 DOM

  • 读取 HTML 文档,构建 DOM 树的过程中,遇到外联的样式声明或脚本声明,如果未设置 deferasync 属性(见下文相关补充),则暂停文档解析,创建新的网络连接,开始下载样式文件和脚本文件。
  • 根据 HTML 的内容,将标签按照结构解析成为 DOM 树,DOM 树解析的过程是一个深度优先遍历。即先构建当前节点的所有子节点,再构建下一个兄弟节点。

根据 CSS 解析生成 CSS 规则树

  • 当读取到 CSS 相关的文件时,就会触发 CSS 规则树的解析。
  • 直到 CSS 规则树和 DOM 树都解析完成前,浏览器都不会进行渲染。

CSS 规则树附着到 DOM 树上 ,构造生成渲染(Render)树

  • CSS 规则树和 DOM 树都解析完成后,就会把 CSS 规则树附着到 DOM 树上构造出浏览器需要渲染的 Render 树。
  • 如果 DOM 树先于 CSS 规则树构建完成,则在 CSS 规则树构建完成后,页面会发生一次重绘,将新构建的 CSS 规则应用于渲染树。

根据渲染树计算每一个节点的信息

  • 布局:通过渲染树中渲染对象的信息,解析 positionoverflowz-indexdisplay等等属性,计算每一个渲染树节点的位置和大小。
  • 回流:布局完成后,发现了某个部分发生了变化影响了布局,那就需要倒回去重新渲染。最后调用操作系统的 Native GUI API 完成绘制(repain)。
  • 渲染树的节点:Gecko中称为 frame,而在 webkit 中称为 renderer.

根据计算好的信息绘制页面

  • 绘制阶段,系统会遍历呈现树,并调用呈现器的 “paint”方法,将呈现器的内容显示在屏幕上。

script 标签 deferasync 属性补充

  • defer:开启新的线程下载脚本文件,并使脚本在文档解析完成后执行。
    • defer 只适用于外联脚本,如果 script 标签没有指定 src 属性,只是内联脚本,不要使用 defer
    • 多个声明 defer 的脚本,则会按顺序下载和执行。
    • defer 脚本会在 DOMContentLoadedload 事件之前执行。
  • asyncHTML5 新增属性):异步下载脚本文件,下载完毕立即解释执行代码。
    • 只适用于外联脚本,这一点和 defer 一致。
    • 多个声明 async 的脚本,不能确保彼此的先后顺序,其下载和执行都是异步的。
    • async 会在 load 事件之前执行,但并不能确保与 DOMContentLoaded 的执行先后顺序。
  • 注:DOMContentLoaded 事件是当初始 HTML 文档完全被加载和解析(即所有的 DOM 完全解析)时触发的,无需要等待样式表,图片,子框架完成加载。而 onload 事件要等页面所有元素,包括图片以及脚本等全部加载完成才触发,因此它比 DOMContentLoaded 要更晚执行。

回流和重绘

  • 回流
    • Render Tree 中的一部分(或全部)因为元素的几何属性、规模尺寸、布局、隐藏等改变而需要重新构建。每个页面至少需要一次回流,就是在页面第一次加载的时候,这时候是一定会发生回流的,因为要构建 Render Tree
  • 重绘
    • Render Tree 中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如 background-colorcolor等。
  • 回流必将引起重绘,而重绘不一定会引起回流。

TCP 四次挥手断开连接

image.png

  • 过程如下:(主动方 --> 浏览器,被动方 --> 服务器)
    • 第一次挥手:主动方向被动方发送报文,Fin、Ack、Seq,表示已经没有数据传输了。并进入 FIN_WAIT_1 状态
    • 第二次挥手:被动方发送报文,Ack、Seq,表示同意关闭请求。此时主机主动方进入 FIN_WAIT_2 状态
    • 第三次挥手:被动方向主动方发送报文段,Fin、Ack、Seq,请求关闭连接。并进入 LAST_ACK 状态
    • 第四次挥手:主动方向被动方发送报文段,Ack、Seq。然后进入等待 TIME_WAIT 状态。被动方收到主动方的报文段以后关闭连接。主动方等待一定时间未收到回复,则正常关闭

参考链接

往期精彩

「点赞、收藏和评论」

❤️关注+点赞+收藏+评论+转发❤️,创作不易,鼓励笔者创作更好的文章,谢谢🙏大家。