You are on page 1of 9

概要

参考文献
 JavaScript 权威指南:第 18 章 脚本化 HTTP
 MDN:XMLHttpRequest
 MDN:AJAX
 MDN:HTTP 访问控制(CORS)

XMLHttpRequest

概念

XMLHttpRequest 定义了脚本操纵 HTTP 的 API,它是 AJAX(Asynchronous JavaScript


And XML)的一种实现方式。
支持定义 GET,POST,HEAD 等请求方式,并且支持获取多种类型的响应数据。

使用

使用 XMLHttpRequest 创建 HTTP 请求以及接受服务端响应,主要涉及以下几个步骤:


 创建 XMLHttpRequest 实例
 调用实例的 open 方法创建请求连接
 设置请求相关的请求头信息
 调用实例的 send 方法发送请求以及传输数据

1) 创建 XMLHttpRequest 实例
let xhr = new XMLHttpRequest();

2)指定连接
xhr.open('GET', './info.json');

open 接收三个参数:方法、URL、是否异步。
方法,不区分大小写,但通常使用大写字母。
是否异步,也就是请求会被异步处理,如果设置为 false 表示同步请求,这就意味着在请求返
回之前,浏览器将处于阻塞状态,用户无法进行其他操作。

3)设置请求头
如果是 GET 请求,参数需要附加在 URL 上,如果是 POST 请求,通常会涉及到请求头
“Content-Type”在指定请求主体的 MIME 类型。
xhr.setRequestHeader('Content-Type', 'application/json');

如果对相同的头调用多次 setRequestHeader(),新值不会取代之前的旧值,这个请求头将包
含多个值。

4)发送请求
如果是 GET 请求,send 应该传递 null 或者省略该参数,POST 请求的请求数据需要使用 send
传递。其中根据传递数据格式的不同,需要设定与此对应的“Content-Type”。
xhr.send(body);

HTTP 请求各部分有指定顺序,setRequestHeader 方法必须在 open 之后 send 之前调用,否


则会抛出异常。

5)取得响应
 监听方法:
o onreadystatechange:每一次 readyState 属性改变都会触发该事件
 readyState
 0:open 未调用
 1:open 已调用
 2:接收到头信息
 3:接收到响应主体
 4:响应完成
 状态:
o status:状态码(数字),如 200,500
o statusText:状态文本,如"OK"
 获取响应头:结果将过滤 cookie 头
o getResponseHeader()
o getAllResponseHeaders()
 响应主体
o response
o responseText
o responseXML

let xhr = new XMLHttpRequest();


xhr.onreadystatechange = function () {

if (xhr.readyState === 4 && xhr.status === 200) {


let type = xhr.getResponseHeader('Content-Type');

if(type.indexOf('xml') !== -1 && xhr.requestXML){


console.log(xhr.requestXML);

}else if(type === 'application/json'){


console.log(JSON.parse(xhr.requestText));

}else{
console.log(xhr.requestText);

}
}
}

xhr.open('GET', './info.json');
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send();

从代码中可以看到对不同类型响应数据的处理:
 响应数据为 XML、XHTML 时
o 可以通过 requestXML 获取一个解析过的 Document 对象
 响应数据为 JSON 等字符串数据时
o 可以通过 requestText 获取文本类型的数据

服务器响应正确解码的前提是,服务端返回了 Content-Type 并设定了正确的 MIME 类型。如


果没有返回 Content-Type 或者 Content-Type 包含了错误的参数,XMLHttpRequest 将不能
正确的解析响应数据。
如果你比服务器更了解资源的 MIME 类型,那么可以调用 overrideMimeType 方法,它将覆写
服务端返回的 Content-Type,例如你将下载 XML 文件,但你计划把它当作纯文本对待。
xhr.overrideMimeType('text/plain; charset=utf-8');

overrideMimeType 和 responseType 的区别是什么?

XMLHttpRequest 请求本地文件,显式跨域问题?
XMLHttpRequest 需要和 HTTP 和 HTTPS 协议一起使用。
当通过相对路径调用本地文件时,这些 URL 将相对于 file://URL 而不是 http://URL。
而同源策略通常会阻止使用绝对 http://URL。
所以在访问本地文件时,通常需要把文件上传到服务器,或者使用本地服务器。

编码请求主体
1) 表单编码

如果直接使用 form 提交,数据默认使用该编码类型,如果我们使用脚本提交,就需要手动添


加该请求头。
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');

当设置以上类型的 MIME 类型时,请求主体需要进行如下格式的编码:


name=lin&age=18&sex=0

编码函数实现:
function encodeFormData(data){
if(!data) return "";
let pairs = [];
for (let name in data) {
if(!data.hasOwnProperty(name)) continue;
if(typeof data[name] === "function") continue;
let value = data[name].toString();
name = encodeURIComponent(name.replace("%20", "+"));
value = encodeURIComponent(value.replace("%20", "+"));
pairs.push(name + "=" + value);
}
return pairs.join("&");
}

请求实现:
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send(encodeFormData({ 'name': 'lin', 'age': '18', 'sex': '0' }))

Jquery 实现了对表单的序列化方法:serialize():
$("button").click(function(){
$("div").text($("form").serialize());
});

2) JSON 编码
JSON 的传输非常简单,只需要使用 JSON.stringify 进行序列化即可。
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.send(JSON.stringify({ 'name': 'lin', 'age': '18', 'sex': '0' }));

3) XML 编码
XMLHttpRequest 的 send 方法,可以传入 XML document 对象,而且不需要设定 Content-
Type,因为 XMLHttpRequest 对象会自动设置一个合适的头。
如果给 send 方法传入一个字符串,但没有指定 Content-Type 头时,XMLHttpRequest 默认
添加“text/plain; charset=UTF-8”。
字符串类型主体,通常会涉及三种编码类型:
 text/plain
 application/json
 application/x-www-form-urlencoded
所以需要额外知道编码的类型,但对于其他可辨识的编码类型,可以忽略对 Content-Type 的
设置。

4) 上传文件(Blob)
XMLHttpRequest 的 send 方法,可以传入 File 对象来实现文件上传。
fileInput.addEventListener("change", function(){
let file = this.files[0];
if(!file) return;
let xhr = new XMLHttpRequest();
xhr.open("POST", "/upload/path");
xhr.send(file);
}, false);

文件类型是通用的二进制大对象(Blob)类型中的一个子类型。XHR2 允许向 send 方法传入


任何 Blob 对象。

5) FormData
当 HTML 表单同时包含文件上传元素和其他元素时,浏览器必须以“multipart/form-data”的
形式使用 POST 来提交表单,这种编码包括使用长边界字符串把请求主体分离成多个部分。
XHR2 定义了 FormData API,来实现多部分请求主体。需要先创建一个 FormData 实例,然
后通过实例的 append 方法添加数据,它可以是字符串、File 或 Blob 对象,然后将 FormData
对象传递给 send 方法。

同样,不需要手动设置“Content-Type“。
let formData = new FormData();
for (let name in data) {
if (!data.hasOwnProperty(name)) continue;
if (typeof data[name] === "function") continue;
formData.append(name, data[name]);
}
xhr.send(formData);

HTTP 进度事件
XMLHttpRequest 对象在请求的不同阶段触发不同类型的事件,在这些事件中不再需要检查
readyState。
 onload:请求完成(完成的请求不一定是成功的请求,需判断状态码 status)
 onloadend:请求结束(触发 load、abort、error、timeout 事件后执行)
 onloadstart:请求接收到响应数据
 onprogress:请求进程
 onabort:请求中止
 onerror:请求错误
 ontimeout:请求超时
onload 表示请求完成,但不一定请求成功,那么 onerror 时在什么时候执行?

下载进度事件
与 progress 事件关联的事件对象有三个有用的属性:
 loaded:表示目前传输的字节数值
 total:表示自"Content-Length"头传输的数据的整体长度
 lengthComputable:表示是否知道内容长度
具体的实现如下:
xhr.onprogress = function(e){
if(e.lengthComputable){
progress.innerHTML = Math.round(100 * e.loaded / e.total) + "% Complete";
}
}

上传进度事件
XMLHttpRequest 对象拥有 upload 属性,它是一个对象,定义了 addEventListener()方法和
整个 progress 事件集合。xhr.onprogress 监控响应的下载进度,xhr.upload.onprogress 监
控请求的上传进度。
xhr.upload.onprogress = function(e){
if(e.lengthComputable){
progress.innerHTML = Math.round(100 * e.loaded / e.total) + "% Complete";
}
}

中止请求和超时

中止请求
 方法:调用实例的 abort()方法触发 abort 事件
 原因:请求超时或者响应数据已不被需要
 场景:用户检索,当用户在服务器响应之前又输入新的检索内容,此时的响应数据已
经过时

超时
 方法:设置实例的 timeout 属性指定超时时间(ms),超时自动触发 timeout 事件
 原因:响应时间超过 timeout 设定的时间

xhr.timeout = 3000;
xhr.ontimeout = function () {
console.log('请求超时');
xhr.abort();
}
xhr.onabort = function () {
console.log('请求中止');
}

跨域 HTTP 请求
在<form>和<iframe>元素中使用跨域 URL,浏览器显示最终的跨域文档,但因为同源策略,
浏览器不允许原始脚本查找跨域文档的内容。使用 XMLHttpRequest,文档内容都是通过
responseText 属性暴露,所以同源策略不允许 XMLHttpRequest 进行跨域请求。 --
[JavaScript 权威指南.P503]
理解:同源策略限制脚本获取跨域文档的内容,这一点同样适用于 XHR,在 XHR 中
responseText 既是对应的跨域文档内容,因此 XHR 也会收到同源策略的限制,不允许直接进
行跨域请求。

XHR2 通过在 HTTP 响应中选择发送合适的 CORS(Cross-Origin Resource Sharing,跨域资


源共享)允许跨域访问网站。
跨域访问的安全细节:
 通过 open 方法传入的用户名和密码,绝对不会通过跨域请求发送(分布式密码破解攻
击)
 跨域请求默认不携带其他任何的用户证书,如 cookie
 跨域响应中的 cookie 会被丢弃
如果跨域请求需要借助用户凭证,如 cookie 等信息,则需要设置 withCredentials 属性为
true。
xhr.withCredentials = true;

API
属性
XMLHttpRequest.onreadystatechange 当 readyState 改变时,调用的处理函数

XMLHttpRequest.readyState 请求状态码 [0, 1, 2, 3, 4]

XMLHttpRequest.response 响应实体,类型取决于 responseType

XMLHttpRequest.responseText 返回 DOMString

XMLHttpRequest.responseType 定义响应类型

XMLHttpRequest.responseURL 返回经过序列化(serialized)的响应 URL

XMLHttpRequest.responseXML 返回一个 Document

XMLHttpRequest.status 数字,代表请求的响应状态,200,304...

XMLHttpRequest.statusText 文本,完整的响应文本,"200 OK"

XMLHttpRequest.timeout 数字,表示最大请求时间(ms)

XMLHttpRequest.upload 对象,表示上传进度

XMLHttpRequest.withCredentials 布尔值,指定跨域请求是否携带授权信息

方法
XMLHttpRequest.abort() 中止请求

XMLHttpRequest.getAllResponseHeaders() 获取所有响应头

XMLHttpRequest.getResponseHeader() 获取指定响应头

XMLHttpRequest.open() 初始化一个请求

XMLHttpRequest.overrideMineType() 覆写服务器返回的 MIME 类型

XMLHttpRequest.send() 发送请求

XMLHttpRequest.setRequestHeader() 设置请求头,open 之后 send 之前调用

事件
abort 请求停止时触发,可使用 onabort 属性

error 请求错误时触发,可使用 onerror 属性

load 请求成功时触发,可使用 onload 属性

loadend 请求结束时触发,可使用 onloadend 属性


loadstart 接受到响应数据时触发,可使用 onloadstart 属

progress 接受到更多数据时,周期性触发,可使用
onprogress 属性

timeout 请求超时时触发,可使用 ontimeout 属性

readyState
值 状态 描述

0 UNSENT 代理被创建,但尚未调用 open() 方法。

1 OPENED open() 方法已经被调用。

2 HEADERS_RECEIVED send() 方法已经被调用,并且头部和状态已


经可获得。

3 LOADING 下载中; responseText 属性已经包含部分


数据。

4 DONE 下载操作已完成。

You might also like