网络请求
XMLHttpRequest 对象
XMLHttpRequest(XHR)对象是一种允许客户端和服务器之间进行异步数据交换的技术,而不必重新加载整个页面。这对于创建更动态、响应更迅速的Web应用程序非常有用。
所有现代浏览器都通过 XMLHttpRequest 构造函数原生支持 XHR 对象
let xhr = new XMLHttpRequest();
使用 XHR
使用 XHR 对象首先要调用 open()
方法,这个方法接收 3 个参数:请求类型("get"、"post"等)、请求 URL,以及表示请求是否异步的布尔值。其次,调用 open()
不会实际发送请求,只是为发送请求做好准备。要发送定义好的请求, 必须在其后面调用 send()
方法。
let xhr = new XMLHttpRequest();
xhr.open('get', 'https://example.com', false);
xhr.send(null);
send()
方法接收一个参数,是作为请求体发送的数据。如果不需要发送请求体,则必须传 null,因为这个参数在某些浏览器中是必需的。调用 send()
之后,请求就会发送到服务器。
因为这个请求是同步的,所以 JavaScript 代码会等待服务器响应之后再继续执行。收到响应后,XHR对象的以下属性会被填充上数据。
-
responseText
:作为响应体返回的文本。 -
responseXML
:如果响应的内容类型是text/xml
或application/xml
,那就是包含响应数据的 XML DOM 文档。 -
status
:响应的 HTTP 状态。 -
statusText
:响应的 HTTP 状态描述。
let xhr = new XMLHttpRequest();
xhr.open('get', 'https://example.com', false);
xhr.send(null);
// 通常这个范围的状态码是正确的响应
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
alert(xhr.responseText);
} else {
alert('Request was unsuccessful: ' + xhr.status);
}
无论是什么响应内容类型,responseText 属性始终会保存响应体,而 responseXML 则对于非 XML 数据是 null。
发送同步请求会阻塞线程,多数情况下最好使用异步请求。
XHR 对象有一个 readyState
属性,表示当前处在请求/响应过程的哪个阶段。这个属性有如下可能的值。
- 0: 未初始化(Uninitialized)。尚未调用
open()
方法。 - 1: 已打开(Open)。已调用
open()
方法,尚未调用send()
方法。 - 2: 已发送(Sent)。已调用
send()
方法,尚未收到响应。 - 3: 接收中(Receiving)。已经收到部分响应。
- 4: 完成(Complete)。已经收到所有响应,可以使用了。
每次 readyState
从一个值变成另一个值,都会触发 readystatechange
事件。可以借此机会检查 readyState
的值。一般来说,我们唯一关心的 readyState
值是 4,表示数据已就绪。为保证跨浏览器兼容,onreadystatechange
事件处理程序应该在调用 open()
之前赋值。
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
alert(xhr.responseText);
} else {
alert('Request was unsuccessful: ' + xhr.status);
}
}
};
// 默认 open 方法第三个参数值 true
xhr.open('get', 'https://example.com', true);
xhr.send(null);
在收到响应之前如果想取消异步请求,可以调用 abort()
方法:
xhr.abort();
调用这个方法后,XHR 对象会停止触发事件,并阻止访问这个对象上任何与响应相关的属性。中断请求后,应该取消对 XHR 对象的引用。由于内存问题,不推荐重用 XHR 对象。
HTTP 头部
每个 HTTP 请求和响应都会携带一些头部字段,这些字段可能对开发者有用。XHR 对象会通过一些方法暴露与请求和响应相关的头部字段。默认情况下,XHR 请求会发送以下头部字段。
-
Accept
:浏览器可以处理的内容类型。 -
Accept-Charset
:浏览器可以显示的字符集。 -
Accept-Encoding
:浏览器可以处理的压缩编码类型。 -
Accept-Language
:浏览器使用的语言。 -
Connection
:浏览器与服务器的连接类型。 -
Cookie
:页面中设置的 Cookie。 -
Host
:发送请求的页面所在的域。 -
Referer
:发送请求的页面的 URI。注意,这个字段在 HTTP 规范中就拼错了,所以考虑到兼容性也必须将错就错。(正确的拼写应该是 Referrer。) -
User-Agent
:浏览器的用户代理字符串。
Referer 请求头可能暴露用户的浏览历史,涉及到用户的隐私问题。有些安全敏感的场景,用户隐私或防范 CSRF 攻击的考虑可能导致浏览器不发送 Referer 头部。
虽然不同浏览器发送的确切头部字段可能各不相同,但这些通常都是会发送的。如果需要发送额外的请求头部,可以使用 setRequestHeader()
方法。这个方法接收两个参数:头部字段的名称和值。为保证请求头部被发送,必须在 open()
之后、send()
之前调用 setRequestHeader()
,如下面的例子所示:
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState == 4) {
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
alert(xhr.responseText);
} else {
alert('Request was unsuccessful: ' + xhr.status);
}
}
};
xhr.open('get', 'https://example.com', true);
xhr.setRequestHeader('MyHeader', 'MyValue');
xhr.send(null);
可以使用 getResponseHeader()
方法从 XHR 对象获取响应头部,只要传入要获取头部的名称即可。如果想取得所有响应头部,可以使用 getAllResponseHeaders()
方法,这个方法会返回包含所有响应头部的字符串。
let myHeader = xhr.getResponseHeader('MyHeader');
let allHeaders = xhr.getAllResponseHeaders();
服务器可以使用头部向浏览器传递额外的结构化数据。getAllResponseHeaders()
方法通常返回类似如下的字符串:
Date: Sun, 14 Nov 2004 18:04:03 GMT
Server: Apache/1.3.29 (Unix)
Vary: Accept
X-Powered-By: PHP/4.3.8
Connection: close
Content-Type: text/html; charset=iso-8859-1
GET 请求
最常用的请求方法是 GET 请求,用于向服务器查询某些信息。必要时,需要在 GET 请求的 URL后面添加查询字符串参数。对 XHR 而言,查询字符串必须正确编码后添加到 URL 后面。
xhr.open('GET', 'https://example.com');
查询字符串中的每个名和值都必须使用 encodeURIComponent()
编码,所有名/值对必须以和号(&)分隔。
xhr.open('get', 'https://example.com?name1=value1&name2=value2', true);
function addURLParam(url, name, value) {
url += url.indexOf('?') == -1 ? '?' : '&';
url += encodeURIComponent(name) + '=' + encodeURIComponent(value);
return url;
}
这里使用 addURLParam()函数可以保证通过 XHR 发送请求的 URL 格式正确。
let url = 'https://example.com';
// 添加参数
url = addURLParam(url, 'name', 'Nicholas');
url = addURLParam(url, 'book', 'Professional JavaScript');
// 初始化请求
xhr.open('get', url, true);
POST 请求
第二个最常用的请求是 POST 请求,用于向服务器发送应该保存的数据。每个 POST 请求都应该请求体中携带提交的数据,而 GET 请求则不然。POST 请求的请求体可以包含非常多的数据,而且数据可以是任意格式。
xhr.open('post', 'https://example.com', true);
接下来就是要给 send(body)
方法传入要发送的请求体。因为 XHR 最初主要设计用于发送 XML,所以可以传入序列化之后的 XML DOM 文档作为请求体。当然,也可以传入任意字符串。
body
可以是 Blob
, FormData
, URLSearchParams
, 或者 USVString
(常用) 对象。设置正确的请求头 xhr.setRequestHeader
才能正确传递参数。参数格式为字符串键值对或JSON字符串。
当 body
是 FormData
对象时,通常不需要手动设置请求头。FormData
会自动设置正确的请求头,包括边界字符串,以便正确传递表单数据。
当 body
是 Blob
对象时,请求头通常会根据文件类型自动设置。
// 提交表单时使用的内容类型
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
// JSON键值对字符串的内容类型
xhr.setRequestHeader('Content-Type', 'application/json');
// 发送二进制数据, 通常用于通过 POST 请求上传文件或提交表单数据
xhr.setRequestHeader('Content-Type', 'multipart/form-data');
application/x-www-form-urlencoded
用于将表单数据进行 URL 编码,并将其包含在请求体中发送到服务器。这种编码方式通常用于通过 HTML 表单提交数据,是默认的 HTML 表单编码类型。
数据被格式化为键值对,键名和键值之间用 =
符号连接,不同的键值对之间用 &
符号连接。空格会被转换为 +
符号或 %20
。
const xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
console.log(xhr);
}
};
xhr.open('POST', 'https://example.com');
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send('username=小白&age=18');
application/json
用于发送 JSON 格式的数据到服务器。JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://example.com', true);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onreadystatechange = function () {
// 省略判断代码
};
// 准备要发送的数据
const body = {
key1: 'value1',
key2: 'value2',
};
// 将数据转换为 JSON 字符串
var jsonBody = JSON.stringify(body);
xhr.send(jsonBody);
multipart/form-data
是一种 HTTP 请求体媒体类型,通常用于在客户端和服务器之间传输二进制数据,例如文件上传。这种编码类型允许将多个部分(multipart parts)组合到一个请求体中,并且每个部分都有自己的头信息,如 Content-Disposition 和 Content-Type。
function uploadFile() {
const fileInput = document.getElementById('fileInput');
const file = fileInput.files[0];
if (file) {
const formData = new FormData();
formData.append('fileInput', file);
const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://example.com/upload', true);
// 不需要手动设置请求头,指定编码类型为 multipart/form-data,
// 因为 FormData 会自动生成正确的 Content-Type
xhr.onreadystatechange = function () {};
xhr.send(formData);
} else {
alert('Please select a file to upload.');
}
}
使用 FormData
对象的优势在于,它会自动处理数据的编码和格式化,同时确保生成的请求体满足 multipart/form-data
的要求。开发者只需要通过 FormData
的 API 添加数据项,而不必担心手动构建整个请求体的格式。
XMLHttpRequest Level 2
XHR 对象作为事实标准的迅速流行,也促使 W3C 为规范这一行为而制定了正式标准。XMLHttpRequest Level 1 只是把已经存在的 XHR 对象的实现细节明确了一下。XMLHttpRequest Level 2又进一步发展了 XHR 对象。并非所有浏览器都实现了 XMLHttpRequest Level 2 的所有部分,但所有浏览器都实现了其中部分功能。