Description
前言
一直都Ajax都比较模糊。就了解一些简单的。但是又觉得好复杂,一点都不想知道,所以想到了一个方法,边看书,边把每一个字都打出来。这样就会强迫自己学下来。这个方法还是很管用的。发现ajax还是不是那么不可理解。慢慢有点理解了。
XMLHttpRequest2级
FormData
FormData类型为序列化表单以及创建表单格式相同的数据(用于通过XHR传输)提供了便利。
看一个例子
var data = new FormData();
data.append('name', 'nico');
append方法,接受两个参数,键和值。分别对应表单字段的名字和字段中包含的值。可以添加任意多个键值对。而通过向FormData构造函数中传入表单元素,也可以用表单元素的数据预先向其中填入键值对。
var data = new FormData(document.form[0]);
创建了FormData的实例后,可以将它直接传给XHR的send方法。例如
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.readyState == 4 ){
if ((xhr.status >= 200 && xhr< 300) || xhr.status == 304) {
console.log(xhr.responseText);
}else{
console.log("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open('post', 'example.php', true);
var form = document.getElementById('user-info');
xhr.send(new FormData(form));
使用FormData的方便在于,不必明确的在XHR对象上设置请求头部。XHR对象能够识别传入的数据类型是FormData的实例。
超时设定
XHR对象添加了一个timeout的属性,表示请求在等待响应多少毫秒之后就终止。在给timeout设置一个数组后,如果在规定的时间内浏览器还没有接收到响应,那么就会触发timeout事件,进而会调用ontimeout事件处理程序。
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.readyState == 4 ){
try {
if ((xhr.status >= 200 && xhr< 300) || xhr.status == 304) {
console.log(xhr.responseText);
}else{
console.log("Request was unsuccessful: " + xhr.status);
}
} catch(ex) {
//由ontimeout事件处理程序处理
}
}
};
xhr.open('get', 'timeout.php', true);
xhr.timeout = 1000;
//将超时设置为1秒,仅适用IE8+
xhr.ontimeout = function() {
console.log('request did not return in a second');
xhr.send(null);
}
在这个例子中,表示如果请求在1秒钟内没有返回,就自动终止。请求终止时,会调用ontimeout事件处理程序,但此时readyState可能已经改变为4,这意味着会调用onreadystatechange事件处理程序,可是,如果在超时终止请求之后再访问status属性,就会导致错误。为了避免浏览器的错误报告,可以将检查status属性的语句封装在一个try-catch语句中。
overrideMimeType()方法
该方法用于重写XHR响应的MIME类型。例如,服务器返回的MIME类型是text/plain,但数据中实际包含的是XML。根据MIME类型,即使数据是XML,responseXML属性中仍然是null,通过调用该方法,可以保证把响应当做XML而非纯文本来处理。
var xhr = new XMLHttpRequest();
xhr.open('get', 'text.php', true);
xhr.overrideMimeType('text/xml');
xhr.send(null);
在这里,强迫把XHR对象将响应当做XML而非纯文本来处理。调用overrideMimeType()必须在send()方法之前。才能保证重写响应的MIME类型。
进度事件
Progress Events规范是W3C的一个工作草案,定义了与客户端服务器通信有关的事件。这些事件最早其实只针对XHR操作,但目前也被其他API借鉴。有以下6个事件。
- loadstart: 在接收到响应数据的第一个字节时触发
- progress: 在接受响应期间持续不断地触发
- error: 请求发生错误时触发
- abort: 在因为调用abort()方法而终止连接时触发。
- load: 在接收到完整的响应数据而出触发
- loaded: 在通信完成或者触发error,abort或load事件后触发
每一个请求都是从loadstart事件开始,接下来是一个或者多个progress事件,然后触发error,abort,或者load事件中的一个,最后触发loaded事件结束。
load事件
load事件,用于代替onreadystatechange事件。响应接受完毕后触发load事件,因此没有必要去检查readyState属性。而onload事件处理程序会接受到一个event对象,其target属性就指向XHR对象实例。
因此可以访问XHR对象的所有方法和属性。但是并非所有浏览器都为这个事件实现了适当的事件对象。
所有开发人员不得不像下面这样被迫使用XHR对象变量。
var xhr = new XMLHttpRequest();
xhr.onload = function() {
if((xhr,status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
}else{
alert("Request was unsuccessful: "+ xhr.status);
}
};
xhr.open('get', 'example.php', true);
xhr.send(null);
只有浏览器接受到服务器的响应,不管其状态如何,都会触发load事件。而这意味着必须要检查status属性,才能确定数据是否真的已经可用。
progress事件
这个事件会在浏览器接收新数据期间周期性地触发。而onprogress事件处理程序会接受到一个event对象,其target属性是XHR对象,但包含着三个额外的属性:lengthComputable、position和totalSize.其中,lengthComputable是一个表示进度是否可用的布尔值,position表示已经接收的字节数,totalSize表示根据Content-Length响应头部确定的预期字节数。
这样就可以给用户创建一个进度指示器。
var xhr = new XMLHttpRequest();
xhr.onload = function() {
if((xhr,status >= 200 && xhr.status < 300) || xhr.status == 304){
alert(xhr.responseText);
}else{
alert("Request was unsuccessful: "+ xhr.status);
}
};
xhr.onprogress = function(event) {
var divStatus = document.getElementById('status');
if (event.lengthComputable){
divStatus.innerHTML = 'Received' + event.position + ' of ' + event.totalSize + 'bytes';
}
};
xhr.open('get', 'example.php', true);
xhr.send(null);
为了确保正常执行,必须在调用open()方法之前添加onprogress事件处理程序。
跨源资源共享(Cross-Origin Resource Sharing)
定义了在必须访问跨源资源时,浏览器与服务器之间该如何沟通。CORS基本的思想是:就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或者响应是该成功还是失败。
例如,一个简单的使用GET或者POST发送的请求,它没有自定义的请求头部,而主体内容是text/plain,在发送该请求时,需要给它附加一个额外的Origin头部,其中包含请求页面的源信息(协议,域名,端口)以便服务器根据这个头部信息来决定是否给予响应
Origin: http://www.xxx.net
Origin头部的一个示例
如果服务器认为这个请求可以接受,就在Access-Control-Allow-Origin头部中回发相同的源信息(如果是公共资源,可以回发‘*’)
Access-Control-Allow-Origin: http://www.xxx.net
如果没有这个头部,或者这个头部信息不匹配,浏览器就会驳回请求。注意,请求和响应都不包含cookie信息。
IE对CORS的实现
在IE8中引入了XDR(XDomainRequest)类型,这个对象与XHR类似,但是能实现安全可靠的跨域通信。
它与XHR的区别在于:
- cookie不会随着请求发送,也不会随响应返回
- 只能设置请求头部信息的content-Type字段
- 不能访问响应的头部信息
- 只支持GET和POST请求
被请求的资源可以根据它认为合适的任意数据(用户代理,源页面)来决定是否设置Access-Control-Allow-Origin头部,作为请求的一部分,Origin头部的值表示请求的来源域,以便远程资源明确地识别XDR请求。
XDR对象的使用方法:
- 先创建一个XDomainRequest的实例。
- 调用open方法,该方法只接受两个参数,请求的类型和URL
- 所有的XDR请求都是异步执行,不能用它来创建同步请求。请求返回之后,会触发load事件,响应的数据也会保存在responseText属性中。
var xdr = new XDomainRequest();
xdr.onload = function() {
alert(xdr.responseText);
};
xdr.open('get', 'http://www.xx.net/page/');
xdr.send(null);
- 在接受到响应后,你只能访问响应的原始文本。
- 没有办法确定响应的状态代码。
- 只要响应有效就会触发load事件,如果失败(包括响应中缺少Access-Control-Allow-Origin)就会触发error事件。
除了错误本身之外,没有其他可用的信息可用,因此唯一能够确定的就只要请求为成功。由于导致XDR请求失败的因素很多,因此可以通过onerror事件处理程序来捕获该事件,否则,即使请求失败也不会有任何提示。
var xdr = new XDomainRequest();
xdr.onload = function() {
alert(xdr.responseText);
};
xdr.onerror = function() {
alert("an error occurred.");
}
xdr.open('get', 'http://www.xx.net/page/');
xdr.send(null);
在请求返回之前可以调用abort()方法来终止请求
xdr.abort()
;
与XHR一样,XDR对象也支持timeout属性以及ontimeout事件处理程序。
var xdr = new XDomainRequest();
xdr.onload = function() {
alert(xdr.responseText);
};
xdr.onerror = function() {
alert("an error occurred.");
}
xdr.timeout = 1000;
xdr.ontimeout = function() {
alert("request took too long");
}
xdr.open('get', 'http://www.xx.net/page/');
xdr.send(null);
为了支持POST请求,XDR对象提供了contentType属性,用来表示发送数据的格式。
var xdr = new XDomainRequest();
xdr.onload = function() {
alert(xdr.responseText);
};
xdr.onerror = function() {
alert("an error occurred.");
}
xdr.open('get', 'http://www.xx.net/page/');
xdr.contentType = "application/x-www-form-urlencoded";
xdr.send(null);
其他浏览器对CORS的实现
通过XMLHttpRequest对象实现了对CORS的原生支持。在尝试打开不同来源的资源时,无需额外编写代码,就可以触发这个行为。要请求位于另一个域中的资源,使用标准的XHR对象并在open()方法中传入绝对URL即可。
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
if(xhr.readyState == 4 ){
if ((xhr.status >= 200 && xhr< 300) || xhr.status == 304) {
console.log(xhr.responseText);
}else{
console.log("Request was unsuccessful: " + xhr.status);
}
}
};
xhr.open('get', 'http://www.xx.net/page', true);
xhr.send(null);
与IE中XDR对象不同的是,通过跨域XHR对象可以访问status和statusText属性,而且还支持同步请求,跨域XHR对象也有一些限制。
- 不能使用setRequestHeader()设置自定义头部
- 不能发送和接受cookie
- 调用getAllResponseHeaders()方法总是返回空的字符串
由于无论同源请求还是跨源请求都支持相同的接口,因此本地资源,最好使用相对URL,在访问远程资源再使用绝对URL,这样能消除歧义,避免出现访问限制头部或者本地cookie信息等问题。
Preflighted Requests
CORS通过一种叫Preflighted Requests的透明服务器验证机制支持开发人员使用自定义的头部、GET或POST之外的方法,以及不同类型的主体内容。在使用下列高级选项来发送请求时,就会向服务器发送一个Preflight请求,这种请求使用OPTIONS方法。发送下列头部。
- Origin: 与简单的请求相同
- Access-Control-Request-Method: 请求自身使用的方法
- Access-Control-Request-Headers: (可选)自定义的头部信息,多个头部信息以逗号隔开。
例如:一个带有自定义头部NCZ的使用的POST方法发送的请求
Origin: http://www.nczonline.net Access-Control-Request-Method: POST Access-Control-Request-Headers:NCZ
发送这个请求后,服务器可以决定是否允许这种类型的请求。服务器通过在响应中发送如下头部与浏览器进行沟通。
- Access-Control-Request-Origin: 与简单的请求相同
- Access-Control-Request-Method: 允许的方法,多个方法以逗号分隔
- Access-Control-Request-Headers: 允许的头部信息,多个头部信息以逗号隔开。
- Access-Control-Max-Age: 应该将这个PreFlight请求缓存多长时间(以秒表示)
例如:
Access-Control-Request-Origin: http://www.nczonline.net Access-Control-Request-Method: POST, GET Access-Control-Request-Headers: NCZ Access-Control-Max-Age1728000
Preflight 请求结束后,结果将按照响应中指定的时间缓存起来,而为此付出的代价只是第一次发送这种请求时会多一次HTTP请求。IE10及更早的版本都不支持。
带凭据的请求
默认情况下,跨源请求不提供凭据(cookie, HTTP认证及客户端SSL证明等)。通过将withCredentials属性设置为true。可以指定某个请求应该发送的凭据。如果服务器接受带凭据的请求,会用下面的HTTP头部来响应。
Access-Control-Allow-Credentials: true
如果发送的是带凭据的请求,但是服务器的响应中没有包含这个头部,那么浏览器就不会把响应交给javascript(于是,responseText中将是空字符串,status状态为0,而且会调用onerror()事件处理程序),另外,服务器还可以在Preflight响应中发送这个HTTP头部。表示允许源发送带凭据的请求。
跨浏览器的CORS
检测XHR是否支持CORS的最简单方式,就是检查是否存在withCredentials属性,再结合检测XDomainRequest对象是否存在。就可以兼顾所有浏览器了。
function createCORSRequest(method, url) {
var xhr = new XMLHttpRequest();
if('withCredentials' in xhr) {
// 此时即支持CORS的情况
// 检查XMLHttpRequest对象是否有“withCredentials”属性
// “withCredentials”仅存在于XMLHTTPRequest2对象里
xhr.open(method, url, true);
}else if (typeof XDomainRequest != 'undefined'){
// 否则检查是否支持XDomainRequest,IE8和IE9支持
// XDomainRequest仅存在于IE中,是IE用于支持CORS请求的方式
xhr = new XDomainRequest();
xhr.open(method, url);
}else {
// 否则,浏览器不支持CORS
xhr = null;
}
return xhr;
}
var request = createCORSRequest('get', 'http://www.xxx.net');
if(request ) {
request.onload = function() {
//对request.responseText进行处理
}
request.send();
}esle {
throw new Error('CORS not supported');
}
Firefox、Safari、Chrome中的XMLHttpRequest对象与IE中的XDomainRequest对象类似,都提供够用的接口,因此以上模式还是相当有用的。这两个对象共同的属性和方法如下:
- abort(): 用于停止正在进行的请求。
- onerror: 用于替代onreadystatechange检测错误
- onload: 用于替代onreadystatechange检测成功
- responseText: 用于取得响应内容
- send() 用于发送请求