基础篇 05 AJAX

AJAX 是异步 JavaScript 和 XML(Asynchronous JavaScript And XML),指的是通过 JavaScript 的异步通信,从服务器获取 XML 文档并从中提取数据,再更新当前网页的对应部分(不用刷新整个网页)。AJAX 本身不是一种新技术,而是一个在 2005 年被 Jesse James Garrett 提出的新术语,用来描述一种使用现有技术集合的新方法,包括:HTML 或 XHTML、CSS、JavaScript、DOM、XML、XSLT 以及最重要的 XMLHttpRequest。

当使用结合了这些技术的 AJAX 模型以后, 网页应用能够快速地将增量更新呈现在用户界面上,而不需要重载(刷新)整个页面。这使得程序能够更快地回应用户的操作。

尽管 X 在 AJAX 中代表 XML,但由于 JSON 格式的许多优势,比如更加轻量以及作为 JavaScript 的一部分,目前 JSON 的使用比 XML 更加普遍。JSON 和 XML 都被用于在 AJAX 模型中打包信息。

1999 年,微软公司发布 IE 浏览器 5.0 版,第一次引入新功能:允许 JavaScript 脚本向服务器发起 HTTP 请求。这个功能当时并没有引起注意,直到 2004 年 Gmail 发布和 2005 年 Google Map 发布,才引起广泛重视。2005 年 2 月,AJAX 这个词第一次正式提出。

后来,AJAX 这个词就成为 JavaScript 脚本发起 HTTP 通信的代名词,也就是说,只要用脚本发起通信,就可以叫做 AJAX 通信。W3C 也在 2006 年发布了它的国际标准。

具体来说,AJAX 包括以下几个步骤:

  • 创建 XMLHttpRequest 实例

  • 发出 HTTP 请求

  • 接收服务器传回的数据

  • 更新网页数据

概括起来,就是一句话,AJAX 通过原生的 XMLHttpRequest 对象发出 HTTP 请求,得到服务器返回的数据后,再进行处理。现在,服务器返回的都是 JSON 格式的数据,XML 格式已经过时了,但是 AJAX 这个名字已经成了一个通用名词,字面含义已经消失了。

常用的 AJAX 库有:XMLHttpRequest、Node HTTP、fetch、node-fetch、axios。。。

XMLHTTP

浏览器与服务器之间,采用 HTTP 协议通信。用户在浏览器地址栏键入一个网址,或者通过网页表单向服务器提交内容,这时浏览器就会向服务器发出 HTTP 请求。

XMLHTTP 是一组 API 函数集,可被 JavaScript、JScript、VBScript 以及其它 Web 浏览器内嵌的脚本语言调用,通过 HTTP 在浏览器和 web 服务器之间收发 XML或其它数据。

XMLHTTP 最大的好处在于可以动态地更新网页,它无需重新从服务器读取整个网页,也不需要安装额外的插件。XMLHTTP 是 AJAX 网页开发技术的重要组成部分。除 XML 之外,XMLHTTP 还能用于获取其它格式的数据,如 JSON 或者甚至纯文本。

XMLHttpRequest

XMLHttpRequest(XHR)对象用于客户端与服务器之间的通信。通过 XMLHttpRequest 可以在不刷新页面的情况下请求特定 URL,获取数据。这允许网页在不影响用户操作的情况下,更新页面的局部内容。XMLHttpRequest 在 AJAX 编程中被大量使用。

XMLHttpRequest 对象是 AJAX 的主要接口。尽管名称有 XML 和 Http,实际上,XMLHttpRequest 可以用于获取任何类型的数据(包括字符串和二进制),而不仅仅是 XML。它甚至支持 HTTP 以外的协议(包括 file:// 和 FTP),但可能受到客户端出于安全等原因的限制。

XMLHttpRequest 本身是一个构造函数,可以使用 new 命令生成实例。它没有任何参数。

下面是 XMLHttpRequest 对象简单用法的完整例子:

function sendXMLHttpRequest() {
    const xhr = new XMLHttpRequest()
    xhr.withCredentials = true
    xhr.onreadystatechange = function () {
        // 通信成功时,状态值为 4
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                console.log(xhr.responseText)
            } else {
                console.error(xhr.statusText)
            }
        }
    }
    xhr.onerror = function (e) {
        console.error(xhr.statusText)
    }
    xhr.open('GET', 'http://localhost:3000', true)
    xhr.send(null)
}

XMLHttpRequest的实例属性

1.readyState

返回一个整数,表示实例对象的当前状态。该属性只读。它可能返回以下值:

  • 0:表示 XMLHttpRequest 实例已经生成,但是实例的 open() 方法还没有被调用。

  • 1:表示 open() 方法已经调用,但是实例的 send() 方法还没有调用,仍然可以使用实例的 setRequestHeader() 方法,设定 HTTP 请求的头信息。

  • 2:表示实例的 send() 方法已经调用,并且服务器返回的头信息和状态码已经收到。

  • 3:表示正在接收服务器传来的数据体(body 部分)。这时,如果实例的 responseType 属性等于 text 或者空字符串,responseText 属性就会包含已经收到的部分信息。

  • 4:表示服务器返回的数据已经完全接收,或者本次接收已经失败。

通信过程中,每当实例对象发生状态变化,它的 readyState 属性的值就会改变。这个值每一次变化,都会触发 readyStateChange 事件。

xhr.onreadystatechange = function(){
    if (xhr.readyState === 4){
    }
}

2.onreadystatechange

指向一个监听函数。readystatechange 事件发生时(实例的 readyState 属性变化),就会执行这个属性。另外,如果使用实例的 abort() 方法,终止 XMLHttpRequest 请求,也会造成 readyState 属性变化。

3.response

一个只读属性,表示服务器返回的数据体(即 HTTP 回应的 body 部分)。它可能是任何数据类型,比如字符串、对象、二进制对象等等,具体的类型由 responseType 属性决定。

如果本次请求没有成功或者数据不完整,该属性等于 null。但是,如果 responseType 属性等于 text 或空字符串,在请求没有结束之前(readyState 等于 3 的阶段),response 属性包含服务器已经返回的部分数据。

4.responseType

该属性是一个字符串,表示服务器返回数据的类型。这个属性是可写的,可以在调用 open() 方法之后、调用 send() 方法之前,设置这个属性的值,告诉服务器返回指定类型的数据。

可以取以下值:

  • ”“(空字符串):等同于 text,表示服务器返回文本数据。

  • “arraybuffer”:ArrayBuffer 对象,表示服务器返回二进制数组。

  • “blob”:Blob 对象,表示服务器返回二进制对象。

  • “document”:Document 对象,表示服务器返回一个文档对象。

  • “json”:JSON 对象。

  • “text”:字符串。

上面几种类型之中,text 类型适合大多数情况,而且直接处理文本也比较方便;Document 类型适合返回 HTML / XML 文档的情况,这意味着,对于那些打开 CORS 的网站,可以直接用 AJAX 抓取网页,然后不用解析 HTML 字符串,直接对抓取回来的数据进行 DOM 操作;blob 类型适合读取二进制数据,比如图片文件。

function loadImage() {
    const xhr = new XMLHttpRequest()
    xhr.withCredentials = true
    xhr.open('GET', 'assets/img/20190419141710_4735vxaqwhri_small.jpg', true)
    xhr.responseType = 'blob'
    xhr.onload = function (e) {
        const img = document.createElement('img')
        img.style.width = '100%'
        img.src = window.URL.createObjectURL(this.response)
        document.body.appendChild(img)
    }
    xhr.send()
}

查看DEMO

5.responseText

返回从服务器接收到的字符串,该属性为只读。只有 HTTP 请求完成接收以后,该属性才会包含完整的数据。

6.responseXML

返回从服务器接收到的 HTML 或 XML 文档对象,该属性为只读。如果本次请求没有成功,或者收到的数据不能被解析为 XML 或 HTML,该属性等于 null。

该属性生效的前提是 HTTP 回应的 Content-Type 头信息等于 text/xmlapplication/xml。这要求在发送请求前,responseType 属性要设为 document。如果 HTTP 回应的 Content-Type 头信息不等于 text/xmlapplication/xml,但是想从 responseXML 拿到数据(即把数据按照 DOM 格式解析),那么需要手动调用 overrideMimeType() 方法,强制进行 XML 解析。

该属性得到的数据,是直接解析后的文档 DOM 树。

const xhr = new XMLHttpRequest()
xhr.open('GET', '/server', true)
xhr.responseType = 'document'
xhr.overrideMimeType('text/xml')
xhr.onload = function () {
    if (xhr.readyState === 4 && xhr.status === 200) {
        console.log(xhr.responseXML)
    }
}
xhr.send(null)

7.responseURL

该属性是字符串,表示发送数据的服务器的网址。

注意: 这个属性的值与 open() 方法指定的请求网址不一定相同。如果服务器端发生跳转,这个属性返回最后实际返回数据的网址。另外,如果原始 URL 包括锚点,该属性会把锚点剥离。

8.status

一个只读属性,返回一个整数,表示服务器回应的 HTTP 状态码。一般来说,如果通信成功的话,这个状态码是200;如果服务器没有返回状态码,那么这个属性默认是200。请求发出之前,该属性为 0 。

  • 200:OK,访问正常

  • 301:Moved Permanently,永久移动

  • 302:Move temporarily,暂时移动

  • 304:Not Modified,未修改

  • 307:Temporary Redirect,暂时重定向

  • 401:Unauthorized,未授权

  • 403:Forbidden,禁止访问

  • 404:Not Found,未发现指定网址

  • 500:Internal Server Error,服务器发生错误

9.statusText

一个只读属性,返回一个字符串,表示服务器发送的状态提示。不同于status属性,该属性包含整个状态信息,比如 OK Not Found 。在请求发送之前(即调用 open() 方法之前),该属性的值是空字符串;如果服务器没有返回状态提示,该属性的值默认为 OK

10.timeout

返回一个整数,表示多少毫秒后,如果请求仍然没有得到结果,就会自动终止。如果该属性等于0,就表示没有时间限制。

11.ontimeout

用于设置一个监听函数,如果发生 timeout 事件,就会执行这个监听函数。

12.事件监听属性

XMLHttpRequest 对象可以对以下事件指定监听函数。

  • onloadstart:loadstart 事件(HTTP 请求发出)的监听函数

  • onprogress:progress事件(正在发送和加载数据)的监听函数

  • onabort:abort 事件(请求中止,比如用户调用了abort()方法)的监听函数

  • onerror:error 事件(请求失败)的监听函数

  • onload:load 事件(请求成功完成)的监听函数

  • ontimeout:timeout 事件(用户指定的时限超过了,请求还未完成)的监听函数

  • onloadend:loadend 事件(请求完成,不管成功或失败)的监听函数

progress 事件的监听函数有一个事件对象参数,该对象有三个属性:

  • loaded:返回已经传输的数据量

  • total:属性返回总的数据量

  • lengthComputable:属性返回一个布尔值,表示加载的进度是否可以计算。

所有这些监听函数里面,只有 progress 事件的监听函数有参数,其他函数都没有参数。

**注意:**如果发生网络错误(比如服务器无法连通),onerror 事件无法获取报错信息。

13.withCredentials

该属性是一个布尔值,表示跨域请求时,用户信息(比如 Cookie 和认证的 HTTP 头信息)是否会包含在请求之中,默认为 false ,即向 example.com 发出跨域请求时,不会发送 example.com 设置在本机上的 Cookie(如果有的话)。

如果需要跨域 AJAX 请求发送 Cookie,需要 withCredentials 属性设为 true。注意,同源的请求不需要设置这个属性。

为了让这个属性生效,服务器必须显式返回 Access-Control-Allow-Credentials 这个头信息。

Access-Control-Allow-Credentials: true

withCredentials 属性打开的话,跨域请求不仅会发送 Cookie,还会设置远程主机指定的 Cookie。反之,如果 withCredentials 属性没有打开,那么跨域的 AJAX 请求即使明确要求浏览器设置 Cookie,浏览器也会忽略。

注意: 脚本总是遵守同源政策,无法从 document.cookie 或者 HTTP 回应的头信息之中,读取跨域的 Cookie, withCredentials 属性不影响这一点。

14.upload

XMLHttpRequest 不仅可以发送请求,还可以发送文件,这就是 AJAX 文件上传。发送文件以后,通过 upload 属性可以得到一个对象,通过观察这个对象,可以得知上传的进展。主要方法就是监听这个对象的各种事件:loadstart、loadend、load、abort、error、progress、timeout。

当请求一切正常时,相关的事件触发顺序如下:

// 之后每次readyState变化时,都会触发一次
xhr.onreadystatechange

xhr.onloadstart

//上传开始
xhr.upload.onloadstart
xhr.upload.onprogress
xhr.upload.onload
xhr.upload.onloadend

//上传结束
xhr.onprogress
xhr.onload
xhr.onloadend

XMLHttpRequest的实例方法

1.open()

用于指定 HTTP 请求的参数,或者说初始化 XMLHttpRequest 实例对象。它一共可以接受五个参数:

xhr.open(method, url, async, user, password)
  • method:表示 HTTP 动词方法,比如 GET、POST、PUT、DELETE、HEAD 等。

  • url:表示请求发送目标 URL。

  • async:布尔值,表示请求是否为异步,默认为 true。如果设为 false,则 send() 方法只有等到收到服务器返回了结果,才会进行下一步操作。该参数可选。由于同步 AJAX 请求会造成浏览器失去响应,许多浏览器已经禁止在主线程使用,只允许 Worker 里面使用。所以,这个参数不应该轻易设为 false。

  • user:表示用于认证的用户名,默认为空字符串。该参数可选。

  • password:表示用于认证的密码,默认为空字符串。该参数可选。

注意: 如果对使用过 open() 方法的 AJAX 请求,再次使用这个方法,等同于调用 abort(),即终止请求。

2.send()

用于实际发出 HTTP 请求。它的参数是可选的,如果不带参数,就表示 HTTP 请求只包含头信息,也就是只有一个 URL,典型例子就是 GET 请求;如果带有参数,就表示除了头信息,还带有包含具体数据的信息体,典型例子就是 POST 请求。

其参数技多种格式:ArrayBufferView、Blob、Document、String、FormData。

注意: 所有 XMLHttpRequest 的监听事件,都必须在 send() 方法调用之前设定。

3.setRequestHeader()

用于设置浏览器发送的 HTTP 请求的头信息。该方法必须在 open() 之后、send() 之前调用。如果该方法多次调用,设定同一个字段,则每一次调用的值会被合并成一个单一的值发送

xhr.setRequestHeader('Content-Type', 'application/json')

4.overrideMimeType()

用来指定 MIME 类型,覆盖服务器返回的真正的 MIME 类型,从而让浏览器进行不一样的处理。举例来说,服务器返回的数据类型是 text/xml,由于种种原因浏览器解析不成功报错,这时就拿不到数据了。为了拿到原始数据,我们可以把 MIME 类型改成 text/plain,这样浏览器就不会去自动解析,从而我们就可以拿到原始文本了。

xhr.overrideMimeType('text/plain')

注意: 该方法必须在 send() 方法之前调用。

5.getResponseHeader()

返回 HTTP 头信息指定字段的值,如果还没有收到服务器回应或者指定字段不存在,返回 null。该方法的参数不区分大小写。

如果有多个字段同名,它们的值会被连接为一个字符串,每个字段之间使用 “逗号+空格” 分隔。

6.getAllResponseHeaders()

返回一个字符串,表示服务器发来的所有 HTTP 头信息。格式为字符串,每个头信息之间使用 CRLF 分隔(回车+换行),如果没有收到服务器回应,该属性为 null。如果发生网络错误,该属性为空字符串。

7.abort()

用来终止已经发出的 HTTP 请求。调用这个方法以后,readyState 属性变为 4,status 属性变为 0。

XMLHttpRequest 实例的事件

更多现代浏览器,包括 Firefox,除了可以设置 on* 属性外,也提供标准的监听器 addEventListener() API 来监听 XMLHttpRequest 事件。

xhr.addEventListener('readystatechange', () => {})

1.readyStateChange 事件

readyState 属性的值发生改变,就会触发 readyStateChange 事件。

可以通过 onReadyStateChange 属性,指定这个事件的监听函数,对不同状态进行不同处理。尤其是当状态变为 4 的时候,表示通信成功,这时回调函数就可以处理服务器传送回来的数据。

2.progress 事件

上传文件时,XMLHTTPRequest 实例对象本身和实例的 upload 属性,都有一个 progress 事件,会不断返回上传的进度。

3.load 事件、error 事件、abort 事件、loadend 事件

  • load 事件:表示服务器传来的数据接收完毕

  • error 事件:表示请求出错

  • abort 事件:表示请求被中断(比如用户取消请求)

  • loadend 事件:abort、load、error 这三个事件,会伴随一个 loadend 事件,表示请求结束,但不知道其是否成功。

4.timeout 事件

服务器超过指定时间还没有返回结果,就会触发 timeout 事件。

常见问题

AJAX 和 XMLHttpRequest 的正确理解

AJAX 是 asynchronous javascript and XML 的简写,中文翻译是异步的 javascript 和 XML,这一技术能够向服务器请求额外的数据而无须卸载页面,会带来更好的用户体验。它是一种技术方案,但并不是一种新技术。

XMLHttpRequest(XHR)是可用于 Web 浏览器脚本语言(例如 JavaScript)的 API。它用于将 HTTP 或 HTTPS 请求发送到 Web 服务器,并将服务器响应数据加载回脚本中。

就是一句话,ajax 通过原生的 XMLHttpRequest 对象发出HTTP请求,得到服务器返回的数据后,再进行处理。

XMLHttpRequest、axios、fetch 有什么区别?

XMLHttpRequest、axios、fetch 都是常用的 AJAX 库。

XMLHttpRequest

XMLHttpRequest 是最早的用于浏览器与服务器交换数据的方式。它可以使用 JSON、XML、HTML 和 text 文本等格式发送和接收数据,并且允许网页在不影响用户操作的情况下,更新页面的局部内容。

Axios

Axios 是一个基于 Promise 网络请求库,作用于 Node.js 和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和 Node.js 中)。在服务端它使用原生 Node.js http 模块,而在客户端则使用 XMLHttpRequest。

就客户端而言,它是基于 XHR 进行二次封装形成的工具库,主要特性有:

  • 从浏览器创建 XMLHttpRequests

  • 支持 Promise API

  • 拦截、转换请求和响应

  • 取消请求

  • 自动转换 JSON 数据

  • 客户端支持防御 XSRF

Fetch

Fetch 提供了一种合理的方式来跨网络异步获取资源(包括跨域请求)。

Fetch 是一个现代的概念,提供了许多与 XMLHttpRequest 相同的功能,但被设计成更具可扩展性和高效性。Fetch 提供了一个更理想的替代方案,可以很容易地被其他技术使用,例如 Service Workers。

fetch 主要特性:

  • fetch 是基于 Promise 的:无论请求成功与否,它都返回一个 Promise 对象,resolve 对应请求的 Response。该 promise 不会拒绝 Http 的错误状态,即使响应是一个 Http 404 或者 500。

  • fetch 没有设置超时时间。

  • fetch 没有办法原生监测请求的进度。

  • fetch 默认不带 cookie:除非使用了 credentials 的初始化选项。credentials 可选值:include(始终发送用户凭据,包括跨域)、same-origin(同源,则发送用户凭据)、omit(永远不要发送或接收 cookie)。用户凭据是指 cookie、基本 http 身份验证等。

一句话概括: XMLHttpRequest 是最早用于客户端与服务器交互数据的方式,是 W3C 标准;Fetch 可以认为是 XMLHttpRequest 的升级版,也是 W3C 标准,但兼容性稍差;Axios 是对 Node.js http 模块(用于 Node.js)和 XMLHttpRequest(用于浏览器)的封装。

响应头取Date异常

获取请求响应中的Date对象: res.request.getResponseHeader("Date"),在浏览器中报错 Refused to get unsafe header "Date"

原因:默认的请求上, 浏览器只能访问以下默认的响应头

  • Cache-Control

  • Content-Language

  • Content-Type

  • Expires

  • Last-Modified

  • Pragma

解决:想让浏览器能访问到其他的 响应头的话 需要在服务器上设置 Access-Control-Expose-Headers。比如想获取时间 Access-Control-Expose-Headers : 'Date'

常用的几种 Content-Type ?

  • application/x-www-form-urlencoded: 浏览器的原生 form 表单,如果不设置 enctype 属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。该种方式提交的数据放在 body 里面,数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL 转码。

  • multipart/form-data: 该种方式也是一个常见的 POST 提交方式,通常表单上传文件时使用该种方式。

  • application/json: 告诉服务器消息主体是序列化后的 JSON 字符串。

  • text/xml: 该种方式主要用来提交 XML 格式的数据。

客户端(JavaScript)下载服务端返回的文件流

import axios from 'axios'
axios.post("/api/download/excel/stream", {}, {
  responseType: 'blob'
}).then((response) => {
  const a = document.createElement('a')
  const url = URL.createObjectURL(response)
  a.href = url
  a.download = 'abcd.xlsx'
  a.click()
  URL.revokeObjectURL(url)
})

特别注意: 请求参数一定要带上 responseType: 'blob'。这是请求参数,不是请求头部的参数(即,不是在 Header 中)。

参考链接

MDN XMLHttpRequest

阮一峰 JavaScript 标准参考教程(alpha)- AJAX

最后更新于