Hyper Text Transfer Protocol(超文本传输协议),服务器传输超文本到本地浏览器的传送协议。HTTP 是基于 TCP/IP 协议通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。不涉及数据包(packet)传输,主要规定了客户端和服务器之间的通信格式,默认80端口。

# Http的特点

  1. 简单快速:客户向服务器请求服务时,只需传送请求方法和路径。常用GET、HEAD、PUT、DELETE、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。

  2. 灵活:HTTP允许传输任意类型的数据对象。

  3. 无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。

  4. 无状态:HTTP协议是无状态的,HTTP 协议自身不对请求和响应之间的通信状态进行保存。任何两次请求之间都没有依赖关系。 直观地说,就是每个请求都是独立的,与前后请求都没直接联系。协议本身并不保留之前一切的请求或响应报文的信息。这是为了更快地处理大量事务,确保协议的可伸缩性,而特意把 HTTP 协议设计成如此简单的。

# Http报文

Http报文包括请求报文和响应报文两大部分,其中请求报文由 请求行(request line)、请求头(header)、空行和请求体 四个部分组成。而响应报文由 状态行、响应头部、空行和响应体 四个部分组成。

  1. 请求行,用来说明请求类型,要访问的资源以及所使用的HTTP版本。
POST  /chapter17/user.html HTTP/1.1

以上代码中“POST ”代表请求方法,“/chapter17/user.html”表示URI,“HTTP/1.1”代表协议和协议的版本。

  1. 请求头由关键字/值对组成,每行一对,关键字和值用英文冒号“:”分隔。

请求头部通知服务器有关于客户端请求的信息。它包含许多有关的客户端环境和请求正文的有用信息。其中比如:

Host,表示主机名,虚拟主机;
Connection,HTTP/1.1增加的,使用keepalive,即持久连接,一个连接可以发多个请求;
User-Agent,请求发出者,兼容性以及定制化需求。
  1. 最后一个请求头之后是一个空行,这个行非常重要,它表示请求头已经结束,接下来的是请求正文。
  2. 请求体 ,可以承载多个请求参数的数据
name=tom&password=1234&realName=tomson

上面代码,承载着name、password、realName三个请求参数。

# HTTP请求方法

  • GET 请求指定的页面信息,并返回实体主体。
  • HEAD 类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头
  • POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。
  • PUT 从客户端向服务器传送的数据取代指定的文档的内容。由于自身不带验证机制,任何人都可以上传文件,因此存在安全性问题,一般不使用该方法。
  • DELETE 请求服务器删除指定的页面,同样不带验证机制.
  • CONNECT:要求在与代理服务器通信时建立隧道
  • OPTIONS:查询支持的方法,查询指定的 URL 能够支持的方法。会返回 Allow: GET, POST, HEAD, OPTIONS 这样的内容。
  • TRACE:追踪路径,服务器会将通信路径返回给客户端。发送请求时,在 Max-Forwards 首部字段中填入数值,每经过一个服务器就会减 1,当数值为 0 时就停止传输。通常不会使用 TRACE,并且它容易受到 XST 攻击(Cross-Site Tracing,跨站追踪)。主要用于测试或诊断。
  • PATCH:对资源进行部分修改,PUT 也可以用于修改资源,但是只能完全替代原始资源,PATCH 允许部分修改。

# GET与POST区别

  • GET在浏览器回退时是无害的,而POST会再次提交请求
  • GET请求会被浏览器主动缓存,而POST不会,除非手动设置
  • GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留
  • GET请求在URL中传送的参数是有长度限制的,而POST没有限制
  • GET参数通过URL传递,POST放在Request body中
  • GET比POST更不安全,因为参数直接暴露在URL上,所以不能用来传递敏感信息。

# 浏览器输入URL地址到显示页面

  • DNS 域名解析(此处涉及 DNS 的寻址过程),找到网页的存放服务器
  • 建立TCP连接,发送http请求
  • server端接收到http请求,处理,并返回
  • 客户端接收到返回数据,处理数据(渲染页面,执行js)
    • 解析形成dom和cssom=>render tree

# 输入地址栏详解

  • URL 解析
  • DNS 查询
  • TCP 连接
  • 处理请求
  • 接受响应
  • 渲染页面
# URL 解析
  • 地址解析:

首先判断你输入的是一个合法的 URL 还是一个待搜索的关键词,并且根据你输入的内容进行自动完成、字符编码等操作。

  • HSTS

由于安全隐患,会使用 HSTS 强制客户端使用 HTTPS 访问页面。

HSTS

HSTS,HTTP Strict Transport Security,简单说就是强制客户端使用 HTTPS 访问页面。其原理就是:

在服务器响应头中添加 Strict-Transport-Security,可以设置 max-age 用户访问时,服务器种下这个头 下次如果使用 http 访问,只要 max-age 未过期,客户端会进行内部跳转,可以看到 307 Redirect Internel 的响应码 变成 https 访问源服务器 这个过程有效避免了中间人对 80 端口的劫持。但是这里存在一个问题:如果用户在劫持状态,并且没有访问过源服务器,那么源服务器是没有办法给客户端种下 Strict-Transport-Security 响应头的(都被中间人挡下来了)。

  • 其他操作

浏览器还会进行一些额外的操作,比如安全检查、访问限制(之前国产浏览器限制 996.icu)。

  • 检查缓存

关于缓存

缓存常常被用作性能优化的技术方案之一,通过缓存可以有效地减少资源获取的耗时,减少用户的等待时长,从而提升用户的体验。

其中,可以通过 HTTP 协议,设置浏览器对 HTTP 响应资源进行缓存。使用浏览器缓存后,当再发起 HTTP 请求时,如果浏览器缓存发现请求的资源已经被存储,它会拦截请求并返回该资源的副本,不需要再去请求服务端获取资源,因此减少了 HTTP 请求的耗时,同时也能有效地缓解服务端压力。

一般来说,HTTP 缓存只能存储 GET请求的响应内容,对于这些响应内容可能会存在两种情况:

  • 不缓存内容,每次请求的时候都会从服务端获取最新的内容;
  • 设置了缓存内容,则在有效期内会从缓存获取,如果用户刷新或内容过期则去服务端获取最新的内容。

那么,要如何给 GET 请求设置缓存呢?在浏览器中,便是依靠请求和响应中的头信息来控制缓存的。根据缓存的行为,我们可以将它们分为强制缓存和协商缓存两种。

强制缓存, 在规定有效期内,直接使用缓存。可以通过以下的方式使用强制缓存:

服务端通过设置Expires和Cache-Control,和客户端约定缓存内容的有效时间;

若符合缓存条件,浏览器响应HTTP 200(from cache)

协商缓存, 与服务端协商是否使用缓存。可以通过以下的方式使用协商缓存:

服务端通过设置If-Modified-Since和If-None-Match,和客户端约定标识协商缓存的值;

当有效期过后,浏览器将缓存信息中的 Etag 和 Last-Modified 信息,分别使用 If-None-Match 和 If-Modified-Since 请求头设置,提交给服务端。

若符合缓存条件,服务端则响应HTTP 304,浏览器将从缓存读数据。

若以上缓存条件均不符合,服务端响应HTTP 200,返回更新后的数据,同时通过响应头更新 HTTP 缓存设置。

# DNS查询
  1. 浏览器缓存 浏览器会先检查是否在缓存中,没有则调用系统库函数进行查询。
  2. 操作系统缓存 操作系统也有自己的 DNS缓存,但在这之前,会向检查域名是否存在本地的 Hosts 文件里,没有则向 DNS 服务器发送查询请求。
  3. 路由器缓存 路由器也有自己的缓存。
  4. ISP DNS 缓存 ISP DNS 就是在客户端电脑上设置的首选 DNS 服务器,它们在大多数情况下都会有缓存。 根域名服务器查询 在前面所有步骤没有缓存的情况下,本地 DNS 服务器会将请求转发到互联网上的根域,下面这个图很好的诠释了整个流程:

需要注意的点

  • 递归方式:一路查下去中间不返回,得到最终结果才返回信息(浏览器到本地DNS服务器的过程)
  • 迭代方式,就是本地DNS服务器到根域名服务器查询的方式。
  • 什么是 DNS 劫持
  • 前端 dns-prefetch 优化
# TCP 连接

TCP/IP 分为四层,在发送数据时,每层都要对数据进行封装:

  1. 应用层:发送 HTTP 请求

在前面的步骤我们已经得到服务器的 IP 地址,浏览器会开始构造一个 HTTP 报文,其中包括:

请求报头(Request Header):请求方法、目标地址、遵循的协议等等 请求主体(其他参数)

其中需要注意的点:浏览器只能发送 GET、POST 方法,而打开网页使用的是 GET 方法

  1. 传输层:TCP 传输报文

传输层会发起一条到达服务器的 TCP 连接,为了方便传输,会对数据进行分割(以报文段为单位),并标记编号,方便服务器接受时能够准确地还原报文信息。在建立连接前,会先进行 TCP 三次握手。

  1. 网络层:IP协议查询Mac地址

将数据段打包,并加入源及目标的IP地址,并且负责寻找传输路线。 判断目标地址是否与当前地址处于同一网络中,是的话直接根据 Mac 地址发送,否则使用路由表查找下一跳地址,以及使用 ARP 协议查询它的 Mac 地址。

注意:在 OSI 参考模型中 ARP 协议位于链路层,但在 TCP/IP 中,它位于网络层。

  1. 链路层:以太网协议

以太网协议根据以太网协议将数据分为以“帧”为单位的数据包,每一帧分为两个部分:

  • 标头:数据包的发送者、接受者、数据类型
  • 数据:数据包具体内容

Mac 地址:以太网规定了连入网络的所有设备都必须具备“网卡”接口,数据包都是从一块网卡传递到另一块网卡,网卡的地址就是 Mac 地址。每一个 Mac 地址都是独一无二的,具备了一对一的能力。

广播:发送数据的方法很原始,直接把数据通过 ARP 协议,向本网络的所有机器发送,接收方根据标头信息与自身 Mac 地址比较,一致就接受,否则丢弃。 注意:接收方回应是单播。

服务器接受请求:接受过程就是把以上步骤逆转过来。

# 服务器处理请求
  • HTTPD

最常见的 HTTPD 有 Linux 上常用的 Apache 和 Nginx,以及 Windows 上的 IIS。它会监听得到的请求,然后开启一个子进程去处理这个请求。

处理请求:接受 TCP 报文后,会对连接进行处理,对HTTP协议进行解析(请求方法、域名、路径等),并且进行一些验证:

  • 验证是否配置虚拟主机
  • 验证虚拟主机是否接受此方法
  • 验证该用户可以使用该方法(根据 IP 地址、身份信息等)

重定向:假如服务器配置了 HTTP 重定向,就会返回一个 301永久重定向响应,浏览器就会根据响应,重新发送 HTTP 请求(重新执行上面的过程)。

URL 重写:

然后会查看 URL 重写规则,如果请求的文件是真实存在的,比如图片、html、css、js文件等,则会直接把这个文件返回。 否则服务器会按照规则把请求重写到 一个 REST 风格的 URL 上。 然后根据动态语言的脚本,来决定调用什么类型的动态文件解释器来处理这个请求。 以 PHP 语言的 MVC 框架举例,它首先会初始化一些环境的参数,根据 URL 由上到下地去匹配路由,然后让路由所定义的方法去处理请求。

# 浏览器接受响应

浏览器接收到来自服务器的响应资源后,会对资源进行分析。 首先查看 Response header,根据不同状态码做不同的事(比如上面提到的重定向)。 如果响应资源进行了压缩(比如 gzip),还需要进行解压。 然后,对响应资源做缓存。 接下来,根据响应资源里的 MIME 类型去解析响应内容(比如 HTML、Image各有不同的解析方式)。

# 渲染页面

浏览器内核

基本流程:

1.HTML 解析 首先要知道浏览器解析是从上往下一行一行地解析的。 解析的过程可以分为四个步骤:

  1. 解码(encoding) 传输回来的其实都是一些二进制字节数据,浏览器需要根据文件指定编码(例如UTF-8)转换成字符串,也就是HTML 代码。
  2. 预解析(pre-parsing) 预解析做的事情是提前加载资源,减少处理时间,它会识别一些会请求资源的属性,比如img标签的src属性,并将这个请求加到请求队列中。
  3. 符号化(Tokenization) 符号化是词法分析的过程,将输入解析成符号,HTML 符号包括,开始标签、结束标签、属性名和属性值。 它通过一个状态机去识别符号的状态,比如遇到<,>状态都会产生变化。
  4. 构建树(tree construction)

注意:符号化和构建树是并行操作的,也就是说只要解析到一个开始标签,就会创建一个 DOM 节点。

在上一步符号化中,解析器获得这些标记,然后以合适的方法创建DOM对象并把这些符号插入到DOM对象中。

<html>
<head>
    <title>Web page parsing</title>
</head>
<body>
    <div>
        <h1>Web page parsing</h1>
        <p>This is an example Web page.</p>
    </div>
</body>
</html>

浏览器容错进制

你从来没有在浏览器看过类似"语法无效"的错误,这是因为浏览器去纠正错误的语法,然后继续工作。

事件

当整个解析的过程完成以后,浏览器会通过DOMContentLoaded事件来通知DOM解析完成。

  1. CSS 解析 一旦浏览器下载了 CSS,CSS 解析器就会处理它遇到的任何 CSS,根据语法规范解析出所有的 CSS 并进行标记化,然后我们得到一个规则表。 CSS 匹配规则 在匹配一个节点对应的 CSS 规则时,是按照从右到左的顺序的,例如:div p { font-size :14px }会先寻找所有的p标签然后判断它的父元素是否为div。 所以我们写 CSS 时,尽量用 id 和 class,千万不要过度层叠。
  2. 渲染树 其实这就是一个 DOM 树和 CSS 规则树合并的过程。

注意:渲染树会忽略那些不需要渲染的节点,比如设置了display:none的节点。

计算=>通过计算让任何尺寸值都减少到三个可能之一:auto、百分比、px,比如把rem转化为px。

级联=>浏览器需要一种方法来确定哪些样式才真正需要应用到对应元素,所以它使用一个叫做specificity的公式,这个公式会通过:

  • 标签名、class、id
  • 是否内联样式
  • !important

然后得出一个权重值,取最高的那个。

渲染阻塞:当遇到一个script标签时,DOM 构建会被暂停,直至脚本完成执行,然后继续构建 DOM 树。 但如果 JS 依赖 CSS 样式,而它还没有被下载和构建时,浏览器就会延迟脚本执行,直至 CSS Rules 被构建。

CSS 会阻塞 JS 执行,JS 会阻塞后面的 DOM 解析

为了避免这种情况,应该以下原则:

CSS 资源排在 JavaScript 资源前面,JS 放在 HTML 最底部,也就是 </body>

另外,如果要改变阻塞模式,可以使用 defer 与 async,详见:这篇文章 4. 布局与绘制 确定渲染树种所有节点的几何属性,比如:位置、大小等等,最后输入一个盒子模型,它能精准地捕获到每个元素在屏幕内的准确位置与大小。 然后遍历渲染树,调用渲染器的 paint() 方法在屏幕上显示其内容。 5. 合并渲染层 把以上绘制的所有图片合并,最终输出一张图片。 6. 回流与重绘 回流(reflow) 当浏览器发现某个部分发现变化影响了布局时,需要倒回去重新渲染,会从html标签开始递归往下,重新计算位置和大小。 reflow基本是无法避免的,因为当你滑动一下鼠标、resize 窗口,页面就会产生变化。 重绘(repaint) 改变了某个元素的背景色、文字颜色等等不会影响周围元素的位置变化时,就会发生重绘。 每次重绘后,浏览器还需要合并渲染层并输出到屏幕上。 回流的成本要比重绘高很多,所以我们应该尽量避免产生回流。 比如:

display:none 会触发回流,而 visibility:hidden 只会触发重绘。

# JavaScript 编译执行

大致流程

可以分为三个阶段:

  1. 词法分析 JS 脚本加载完毕后,会首先进入语法分析阶段,它首先会分析代码块的语法是否正确,不正确则抛出“语法错误”,停止执行。 几个步骤:

分词,例如将var a = 2,,分成var、a、=、2这样的词法单元。 解析,将词法单元转换成抽象语法树(AST)。 代码生成,将抽象语法树转换成机器指令。

  1. 预编译 JS 有三种运行环境:

全局环境 函数环境 eval

每进入一个不同的运行环境都会创建一个对应的执行上下文,根据不同的上下文环境,形成一个函数调用栈,栈底永远是全局执行上下文,栈顶则永远是当前执行上下文。 创建执行上下文 创建执行上下文的过程中,主要做了以下三件事:

创建变量对象

参数、函数、变量

建立作用域链

确认当前执行环境是否能访问变量

确定 This 指向

  1. 执行 JS 线程

虽然 JS 是单线程的,但实际上参与工作的线程一共有四个:

其中三个只是协助,只有 JS 引擎线程是真正执行的

JS 引擎线程:也叫 JS 内核,负责解析执行 JS 脚本程序的主线程,例如 V8 引擎 事件触发线程:属于浏览器内核线程,主要用于控制事件,例如鼠标、键盘等,当事件被触发时,就会把事件的处理函数推进事件队列,等待 JS 引擎线程执行 定时器触发线程:主要控制setInterval和setTimeout,用来计时,计时完毕后,则把定时器的处理函数推进事件队列中,等待 JS 引擎线程。 HTTP 异步请求线程:通过XMLHttpRequest连接后,通过浏览器新开的一个线程,监控readyState状态变更时,如果设置了该状态的回调函数,则将该状态的处理函数推进事件队列中,等待JS引擎线程执行。

注:浏览器对同一域名的并发连接数是有限的,通常为 6 个。 宏任务 分为:

同步任务:按照顺序执行,只有前一个任务完成后,才能执行后一个任务 异步任务:不直接执行,只有满足触发条件时,相关的线程将该异步任务推进任务队列中,等待JS引擎主线程上的任务执行完毕时才开始执行,例如异步Ajax、DOM事件,setTimeout等。

微任务 微任务是ES6和Node环境下的,主要 API 有:Promise,process.nextTick。 微任务的执行在宏任务的同步任务之后,在异步任务之前。

# DNS 解析

DNS 的全称是 Domain Name System,又称域名系统,它负责把www.qq.com这样的域名地址翻译成一个 IP(比如14.18.180.206),而客户端与服务器建立 TCP 连接需要通过 IP 通信。

在网络中每个终端之间实现连接和通信是通过一个唯一的 IP 地址来完成。在建立 TCP 连接前,需要找到建立连接的服务器,DNS 的解析过程可以让用户通过域名找到存放文件的服务器

DNS 解析过程会进行递归查询,分别依次尝试从以下途径,按顺序地获取该域名对应的 IP 地址。

  • 浏览器缓存
  • 系统缓存(用户操作系统 Hosts 文件 DNS 缓存)
  • 路由器缓存
  • 互联网服务提供商 DNS 缓存(联通、移动、电信等互联网服务提供商的 DNS 缓存服务器)
  • 根域名服务器
  • 顶级域名服务器
  • 主域名服务器

DNS 解析过程会根据上述步骤进行递归查询,如果最终依然没找到,浏览器便会将页面响应为打开失败。

除此之外,在前后端联调过程中也常常需要配置 HOST,这个过程便是修改了浏览器缓存或是系统缓存。通过将特定域名指向自身的服务器 IP 地址,便可以实现通过域名访问本地环境、测试环境、预发布环境的服务器资源。

Host是一个没有扩展名的系统文件,其基本作用是将一些常用的网址域名与其对应的IP地址建立一个关联“数据库”。这个文件通常位于C:\Windows\System32\drivers\etc文件夹中,并且可以用记事本等工具打开进行编辑。

当用户在浏览器中输入一个需要登录的网址时,系统会首先自动从Hosts文件中寻找对应的IP地址。如果找到了,系统会立即打开对应的网页;如果没有找到,则系统会再将网址提交给DNS域名解析服务器进行IP地址的解析。需要注意的是,Hosts文件配置的映射是静态的,也就是说,一旦一个域名和IP地址在Hosts文件中建立了映射关系,如果在网络上的计算机IP地址发生了更改,用户需要及时更新Hosts文件中的对应映射,否则将不能正常访问。

那为什么需要配置域名 HOST,而不直接使用 IP 地址进行访问呢?这是因为浏览器的同源策略会导致跨域问题。

同源策略要求,只有当请求的协议、域名和端口都相同的情况下,才可以访问当前页面的 Cookie/LocalStorage/IndexDB、获取和操作 DOM 节点,以及发送 Ajax 请求。通过同源策略的限制,可以避免恶意的攻击者盗取用户信息,从而可以保证用户信息的安全。

本地调试时,比如使用 npm run dev 起服务,本地地址常常为 localhost 或者是 127.0.0.1,这些地址如果直接发起请求是会跨域且可能被拒绝的,配置 host 很多时候是为了将支持跨域的域名映射到本地,从而可以正常地访问和进行本地调试(代理只能解决本地开发的情况,本地开发更加常用的是配置 host 来解决)

DNS 解析完成后,浏览器获得了服务端的 IP 地址,接下来便可以向服务端发起 HTTP 请求。目前大多数 HTTP 请求都建立在 TCP 连接上,因此客户端和服务端会先建立起 TCP 连接。

# TCP 连接的建立

TCP 连接的建立过程比较偏通信底层,在前端日常开发过程中不容易接触到。但有时候需要优化应用的加载耗时、请求耗时或是定位一些偏底层的问题(请求异常、HTTP 连接无法建立等),都会或多或少依赖这些偏底层的知识。

另外,从面试的角度看,需要掌握 TCP/UDP 的区别、TCP 的三次握手和四次挥手内容。

  • TCP 协议提供可靠传输服务,UDP 协议则可以更快地进行通信;
  • 三次握手:指 TCP 连接的建立过程,该过程中客户端和服务端总共需要发送三个包,从而确认连接存在。
  • 四次挥手:指 TCP 连接的断开过程,该过程中需要客户端和服务端总共发送四个包以,从而确认连接关闭。

当客户端和服务端建立起 TCP 连接之后,HTTP 服务器会监听客户端发起的请求,此时客户端会发起 HTTP 请求。

# HTTP 请求与 TCP 协议

由客户端发起的 HTTP 请求,服务器收到后会进行回复,回复内容通常包括 HTTP 状态、响应消息等,更具体的会在下一讲 HTTP 协议中进行介绍。

前面说过,目前大多数 HTTP 请求都是基于 TCP 协议。TCP 协议的目的是提供可靠的数据传输,它用来确保可靠传输的途径主要包括两个:

  • 乱序重建:通过对数据包编号来对其排序,从而使得另一端接收数据时,可以重新根据编号还原顺序。
  • 丢包重试:可通过发送方是否得到响应,来检测出丢失的数据并重传这些数据。

通过以上方式,TCP 在传输过程中不会丢失或破坏任何数据,这也是即使出现网络故障也不会损坏文件下载的原因。

因此,目前大多数 HTTP 连接基于 TCP 协议。不过,在 HTTP/3 中底层支撑是 QUIC 协议,该协议使用的是 UDP 协议。因为 UDP 协议丢弃了 TCP 协议中所有的错误检查内容,因此可以更快地进行通信,更常用于直播和在线游戏的应用。

也就是说,HTTP/3 基于 UDP 协议实现了数据的快速传输,同时通过 QUIC 协议保证了数据的可靠传输,最终实现了又快又可靠的通信。

除了以上的内容,其实还可以去了解关于 TCP/IP 协议的分层模型、IP 寻址过程,以及 IP 协议又是如何将数据包准确无误地传递这些内容,也需要关注 HTTP/2、HTTP/3、HTTPS 这些协议的设计变更了什么、又解决了什么。

# Ajax 请求

它并不是 JavaScript 的规范,而是 Jesse James Garrett 提出的新术语:Asynchronous JavaScript and XML,意思是用 JavaScript 执行异步网络请求。

# 网络请求的发展

对于浏览器来说,网络请求是用来从服务端获取需要的信息,然后解析协议和内容,来进行页面渲染或者是信息获取的过程。

在很久以前,我们的网络请求除了静态资源(HTML/CSS/JavaScript 等)文件的获取,主要用于表单的提交。我们在完成表单内容的填写之后,点击提交按钮,接下来表单开始提交,浏览器就会刷新页面,然后在新页面里告诉你操作是成功了还是失败了。

除了页面跳转刷新会影响用户体验,在表单提交过程中,使用同步请求会阻塞进程。此时用户无法继续操作页面,导致页面呈现假死状态,使得用户体验变得糟糕。

为了避免这种情况,我们开始使用异步请求 + 回调的方式,来进行请求处理,这就是 Ajax。

随着时间发展,Ajax 的应用越来越广,如今使用 Ajax 已经是前端开发的基本操作。但 Ajax 是一种解决方案,在前端中的具体实现依赖使用XMLHttpRequest相关 API。页面开始支持局部更新、动态加载,甚至还有懒加载、首屏加载等等,都是以XMLHttpRequest为前提。

# XMLHttpRequest

XMLHttpRequest让发送一个 HTTP 请求变得非常容易,只需要简单的创建一个请求对象实例

将其封装起来,同时使用 ES6 的Promise的方式,我可以将其变成一个通过Peomise进行异步回调的请求函数:

function Ajax({ method, url, params, contentType }) {
  const xhr = new XMLHttpRequest();
  const formData = new FormData();
  Object.keys(params).forEach((key) => {
    formData.append(key, params[key]);
  });
  return new Promise((resolve, reject) => {
    try {
      xhr.open(method, url, false);
      xhr.setRequestHeader("Content-Type", contentType);
      xhr.onreadystatechange = function () {
        if (xhr.readyState === 4) {
          if (xhr.status >= 200 && xhr.status < 400) {
            // 这里使用200-400来判断
            resolve(xhr.responseText);
          } else {
            // 返回请求信息
            reject(xhr);
          }
        }
      };
      xhr.send(formData);
    } catch (err) {
      reject(err);
    }
  });
}

通过这样简单的封装,我们就可以以 Promise 的方式来发起 Ajax 请求。

但在具体的项目使用过程中,通常还需要考虑更多的问题,比如防抖节流、失败重试、缓存能力、浏览器兼容性、参数处理等。

这就是 HTTP 请求的编程实现。

参考链接 (opens new window)

最后更新: 2/22/2024, 8:50:01 AM