XMLHttpRequest 和AJAX的爱恨情仇
AJAX 是 asynchronous javascript and XML 的简写,中文翻译是异步的 javascript 和 XML,这一技术能够向服务器请求额外的数据而无须卸载页面,会带来更好的用户体验。虽然名字中包含 XML ,但 AJAX 通信与数据格式无关
AJAX 包括以下几步骤:1、创建 AJAX 对象;2、发出 HTTP 请求;3、接收服务器传回的数据;4、更新网页数据
概括起来,就是一句话,AJAX 通过原生的 XMLHttpRequest 对象发出 HTTP 请求,得到服务器返回的数据后,再进行处理
AJAX 技术的核心是 XMLHttpReques t对象(简称XHR),这是由微软首先引入的一个特性,其他浏览器提供商后来都提供了相同的实现。XHR 为向服务器发送请求和解析服务器响应提供了流畅的接口,能够以异步方式从服务器取得更多信息,意味着用户单击后,可以不必刷新页面也能取得新数据
XMLHttpRequest 对象是当今所有 AJAX 和 Web 2.0 应用程序的技术基础。尽管软件经销商和开源社团现在都在提供各种 AJAX 框架以进一步简化 XMLHttpRequest 对象的使用;但是,我们仍然很有必要理解这个对象的详细工作机制。
IE5 是第一款引入 XHR 对象的浏览器。在 IE5 中,XHR 对象是通过 MSXML 库中的一个 ActiveX 对象实现的,而 IE7+ 及其他标准浏览器都支持原生的XHR对象
一、 引言
AJAX 利用一个构建到所有现代浏览器内部的对象 XMLHttpRequest 来实现发送和接收 HTTP 请求与响应信息。一个经由XMLHttpRequest 对象发送的 HTTP 请求并不要求页面中拥有或回寄一个<form>元素。AJAX 中的"A"代表了"异步",这意味着XMLHttpRequest 对象的 send() 方法可以立即返回,从而让 Web 页面上的其它 HTML/JavaScript 继续其浏览器端处理而由服务器处理HTTP 请求并发送响应。尽管缺省情况下请求是异步进行的,但是,你可以选择发送同步请求,这将会暂停其它Web页面的处理,直到该页面接收到服务器的响应为止。
微软在其 Internet Explorer(IE) 5 中作为一个 ActiveX 对象形式引入了 XMLHttpRequest 对象。其他的认识到这一对象重要性的浏览器制造商也都纷纷在他们的浏览器内实现了 XMLHttpRequest 对象,但是作为一个本地 JavaScript 对象而不是作为一个 ActiveX 对象实现。而如今,在认识到实现这一类型的价值及安全性特征之后,微软已经在其 IE 7中把 XMLHttpRequest 实现为一个窗口对象属性。幸运的是,尽管其实现(因而也影响到调用方式)细节不同,但是,所有的浏览器实现都具有类似的功能,并且实质上是相同方法。目前,W3C组织正在努力进行 XMLHttpRequest 对象的标准化,并且已经发行了有关该W3C规范的一个草案。
二、 XMLHttpRequest对象的属性和事件
前端在接收到响应后,第一步是检查status属性,以确定响应已经成功返回。一般来说,可以将HTTP状态码为200作为成功的标志。此时,responseText属性的内容已经就绪,而且在内容类型正确的情况下,responseXML也可以访问了。此外,状态码为304表示请求的资源并没有被修改,可以直接使用浏览器中缓存的版本;当然,也意味着响应是有效的
无论内容类型是什么,响应主体的内容都会保存到responseText属性中,而对于非XML数据而言,responseXML属性的值将为null
XMLHttpRequest对象暴露各种属性、方法和事件以便于脚本处理和控制HTTP请求与响应。
-
readyState属性
当XMLHttpRequest对象把一个HTTP请求发送到服务器时将经历若干种状态:一直等待直到请求被处理;然后,它才接收一个响应。这样以来,脚本才正确响应各种状态-XMLHttpRequest对象暴露一个描述对象的当前状态的readyState属性。
ReadyState取值 描述
0 描述一种"未初始化"状态;此时,已经创建一个XMLHttpRequest对象,但是还没有初始化。
1 描述一种"发送"状态;此时,代码已经调用了XMLHttpRequest open()方法并且XMLHttpRequest已经准备好把一个请求发送到服务器。
2 描述一种"发送"状态;此时,已经通过send()方法把一个请求发送到服务器端,但是还没有收到一个响应。
3 描述一种"正在接收"状态;此时,已经接收到HTTP响应头部信息,但是消息体部分还没有完全接收结束。
4 描述一种"已加载"状态;此时,响应已经被完全接收。
-
onreadystatechange事件
无论readyState值何时发生改变,XMLHttpRequest对象都会激发一个readystatechange事件。其中,onreadystatechange属性接收一个EventListener值-向该方法指示无论readyState值何时发生改变,该对象都将激活。
-
responseText属性
字符串,只读,请求不成功或者数据不完整,该属性为null。如果返回的是JSON格式的数据,就可以调用responseText属性
这个responseText属性包含客户端接收到的HTTP响应的文本内容。当readyState值为0、1或2时,responseText包含一个空字符串。当readyState值为3(正在接收)时,响应中包含客户端还未完成的响应信息。当readyState为4(已加载)时,该responseText包含完整的响应信息。
-
responseXML属性
此responseXML属性用于当接收到完整的HTTP响应时(readyState为4)描述XML响应;此时,Content-Type头部指定MIME(媒体)类型为text/xml,application/xml或以+xml结尾。如果Content-Type头部并不包含这些媒体类型之一,那么responseXML的值为null。无论何时,只要readyState值不为4,那么该responseXML的值也为null。
其实,这个responseXML属性值是一个文档接口类型的对象,用来描述被分析的文档。如果文档不能被分析(例如,如果文档不是良构的或不支持文档相应的字符编码),那么responseXML的值将为null。
-
status属性
只读属性,HTTP状态码,是一个三位数的整数。其类型为short。而且,仅当readyState值为3(正在接收中)或4(已加载)时,这个status属性才可用。当readyState的值小于3时试图存取status的值将引发一个异常。
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,服务器发生错误只有 2xx和304表示服务器正常。
-
statusText属性
只读属性,一个字符串,表示服务器的状态,是一个完整信息,如 “200 OK”。描述了HTTP状态代码文本;并且仅当readyState值为3或4才可用。当readyState为其它值时试图存取statusText属性将引发一个异常。
-
responseType属性
类型可以是ArrayBuffer(内存缓冲区?不太理解)、Bold(前端的一个对象)、Documnet(Document对象即XML、html文档或html片段)、JSON(JS对象)、text(字符串)。
xhr.responseType="json";
responseType 设置为 json,支持json的浏览器,主流浏览器都支持,会自动调用JSON.parese()方法,将JSON解析为JS对象,就是说 从xhr.response(不是xhr.responseText属性)属性得到的不是文本,是JS对象。
三、 XMLHttpRequest对象的方法
-
abort()方法
你可以使用这个abort()方法来暂停与一个XMLHttpRequest对象相联系的HTTP请求,从而把该对象复位到未初始化状态。
-
open()方法
调用open(DOMString method,DOMString uri,boolean async,DOMString username,DOMString password)方法初始化一个XMLHttpRequest对象。例如
xhr.open("get","example.php", false);
- 第一个参数用于指定发送请求的方式,这个字符串,不区分大小写,但通常使用大写字母。
"GET"用于常规请求,它适用于当URL完全指定请求资源,当请求对服务器没有任何副作用以及当服务器的响应是可缓存的情况下"POST"方法常用于HTML表单。它在请求主体中包含额外数据且这些数据常存储到服务器上的数据库中。相同URL的重复POST请求从服务器得到的响应可能不同,同时不应该缓存使用这个方法的请求除了"GET"和"POST"之外,参数还可以是"HEAD"、"OPTIONS"、"PUT"。而由于安全风险的原因,"CONNECT"、"TRACE"、"TRACK"被禁止使用[注意]关于HTTP协议8种常用方法的详细介绍移步至此
- 第二个参数是URL,该URL相对于执行代码的当前页面,且只能向同一个域中使用相同端口和协议的URL发送请求。如果URL与启动请求的页面有任何差别,都会引发安全错误。借助于window.document.baseURI属性,该uri被解析为一个绝对的URI-换句话说,你可以使用相对的URI-它将使用与浏览器解析相对的URI一样的方式被解析。
- 第三个参数async参数指定是否请求是异步的-缺省值为true。为了发送一个同步请求,需要把这个参数设置为false。
- 对于要求认证的服务器,你可以提供可选的用户名和口令参数。在调用open()方法后,XMLHttpRequest对象把它的readyState属性设置为1(打开)并且把responseText、responseXML、status和statusText属性复位到它们的初始值。另外,它还复位请求头部。注意,如果你调用open()方法并且此时readyState为4,则XMLHttpRequest对象将复位这些值。
在通过调用open()方法准备好一个请求之后,你需要把该请求发送到服务器。仅当readyState值为1时,你才可以调用send()方法;否则的话,XMLHttpRequest对象将引发一个异常。该请求被使用提供给open()方法的参数发送到服务器。当async参数为true时,send()方法立即返回,从而允许其它客户端脚本处理继续。在调用send()方法后,XMLHttpRequest对象把readyState的值设置为2(发送)。当服务器响应时,在接收消息体之前,如果存在任何消息体的话,XMLHttpRequest对象将把readyState设置为3(正在接收中)。当请求完成加载时,它把readyState设置为4(已加载)。对于一个HEAD类型的请求,它将在把readyState值设置为3后再立即把它设置为4。
-
send()方法
send()方法接收一个参数,即要作为请求主体发送的数据。调用send()方法后,请求被分派到服务器
如果是GET方法,send()方法无参数,或参数为null;
如果是POST方法,send()方法的参数为要发送的数据。对于发送数据为其它的数据类型,在调用send()方法之前,应该使用setRequestHeader()方法(见后面的解释)先设置Content-Type头部。如果在send(data)方法中的data参数的类型为DOMString,那么,数据将被编码为UTF-8。如果数据是Document类型,那么将使用由data.xmlEncoding指定的编码串行化该数据。
在POST方法中,向服务器提交的表单数据传入send()中,序列化和转码处理,键值对用&连接,如 "name=jack&age=20",且在send()调用前,需要设置MIME类型:xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
-
setRequestHeader()方法
该setRequestHeader(DOMString header,DOMString value)方法用来设置请求的头部信息。当readyState值为1时,你可以在调用open()方法后调用这个方法;否则,你将得到一个异常。
-
getResponseHeader()方法
getResponseHeader(DOMString header,value)方法用于检索响应的头部值。仅当readyState值是3或4(换句话说,在响应头部可用以后)时,才可以调用这个方法;否则,该方法返回一个空字符串。
-
getAllResponseHeaders()方法
该getAllResponseHeaders()方法以一个字符串形式返回所有的响应头部(每一个头部占单独的一行)。如果readyState的值不是3或4,则该方法返回null。
四、 发送请求
在AJAX中,许多使用XMLHttpRequest的请求都是从一个HTML事件(例如一个调用JavaScript函数的按钮点击(onclick)或一个按键(onkeypress))中被初始化的。AJAX支持包括表单校验在内的各种应用程序。有时,在填充表单的其它内容之前要求校验一个唯一的表单域。例如要求使用一个唯一的UserID来注册表单。如果不是使用AJAX技术来校验这个UserID域,那么整个表单都必须被填充和提交。如果该UserID不是有效的,这个表单必须被重新提交。例如,一个相应于一个要求必须在服务器端进行校验的Catalog ID的表单域可能按下列形式指定:
<form name="validationForm" action="validateForm" method="post">
<table> <tr><td>Catalog Id:</td> <td> <input type="text" size="20" id="catalogId" name="catalogId" autocomplete="off" onkeyup="sendRequest()"> </td> <td><div id="validationMessage"></div></td> </tr>
</table></form>
前面的HTML使用validationMessage div来显示相应于这个输入域Catalog Id的一个校验消息。onkeyup事件调用一个JavaScript sendRequest()函数。这个sendRequest()函数创建一个XMLHttpRequest对象。创建一个XMLHttpRequest对象的过程因浏览器实现的不同而有所区别。如果浏览器支持XMLHttpRequest对象作为一个窗口属性(所有普通的浏览器都是这样的,除了IE 5和IE 6之外),那么,代码可以调用XMLHttpRequest的构造器。如果浏览器把XMLHttpRequest对象实现为一个ActiveXObject对象(就象在IE 5和IE 6中一样),那么,代码可以使用ActiveXObject的构造器。下面的函数将调用一个init()函数,它负责检查并决定要使用的适当的创建方法-在创建和返回对象之前。
<script type="text/javascript">
function sendRequest(){ var xmlHttpReq=init(); function init(){ if (window.XMLHttpRequest) { return new XMLHttpRequest(); }else if (window.ActiveXObject) { return new ActiveXObject("Microsoft.XMLHTTP"); }}
}
</script>
接下来,你需要使用Open()方法初始化XMLHttpRequest对象-指定HTTP方法和要使用的服务器URL。
var catalogId=encodeURIComponent(document.getElementById("catalogId").value);
xmlHttpReq.open("GET", "validateForm?catalogId=" + catalogId, true);
默认情况下,使用XMLHttpRequest发送的HTTP请求是异步进行的,但是你可以显式地把async参数设置为true,如上面所展示的。
在这种情况下,对URL validateForm的调用将激活服务器端的一个servlet,但是你应该能够注意到服务器端技术不是根本性的;实际上,该URL可能是一个ASP,ASP.NET或PHP页面或一个Web服务-这无关紧要,只要该页面能够返回一个响应-指示CatalogID值是否是有效的-即可。因为你在作一个异步调用,所以你需要注册一个XMLHttpRequest对象将调用的回调事件处理器-当它的readyState值改变时调用。记住,readyState值的改变将会激发一个readystatechange事件。你可以使用onreadystatechange属性来注册该回调事件处理器。
xmlHttpReq.onreadystatechange=processRequest;
然后,我们需要使用send()方法发送该请求。因为这个请求使用的是HTTP GET方法,所以,你可以在不指定参数或使用null参数的情况下调用send()方法。
xmlHttpReq.send(null);
五、 处理请求
在这个示例中,因为HTTP方法是GET,所以在服务器端的接收servlet将调用一个doGet()方法,该方法将检索在URL中指定的catalogId参数值,并且从一个数据库中检查它的有效性。
本文示例中的这个servlet需要构造一个发送到客户端的响应;而且,这个示例返回的是XML类型,因此,它把响应的HTTP内容类型设置为text/xml并且把Cache-Control头部设置为no-cache。设置Cache-Control头部可以阻止浏览器简单地从缓存中重载页面。
// java 代码,我不是很懂。。
public void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException { ... ... response.setContentType("text/xml"); response.setHeader("Cache-Control", "no-cache");
}
来自于服务器端的响应是一个XML DOM对象,此对象将创建一个XML字符串-其中包含要在客户端进行处理的指令。另外,该XML字符串必须有一个根元素。
out.println("<catalogId>valid</catalogId>");
【注意】XMLHttpRequest对象的设计目的是为了处理由普通文本或XML组成的响应;但是,一个响应也可能是另外一种类型,如果用户代理(UA)支持这种内容类型的话。
当请求状态改变时,XMLHttpRequest对象调用使用onreadystatechange注册的事件处理器。因此,在处理该响应之前,你的事件处理器应该首先检查readyState的值和HTTP状态。当请求完成加载(readyState值为4)并且响应已经完成(HTTP状态为"OK")时,你就可以调用一个JavaScript函数来处理该响应内容。下列脚本负责在响应完成时检查相应的值并调用一个processResponse()方法。
function processRequest(){if(xmlHttpReq.readyState==4){if(xmlHttpReq.status==200){processResponse();}}
}
该processResponse()方法使用XMLHttpRequest对象的responseXML和responseText属性来检索HTTP响应。如上面所解释的,仅当在响应的媒体类型是text/xml,application/xml或以+xml结尾时,这个responseXML才可用。这个responseText属性将以普通文本形式返回响应。对于一个XML响应,你将按如下方式检索内容:
var msg=xmlHttpReq.responseXML;
借助于存储在msg变量中的XML,你可以使用DOM方法getElementsByTagName()来检索该元素的值:
var catalogId=msg.getElementsByTagName("catalogId")[0].firstChild.nodeValue;
最后,通过更新Web页面的validationMessage div中的HTML内容并借助于innerHTML属性,你可以测试该元素值以创建一个要显示的消息:
if(catalogId=="valid"){var validationMessage = document.getElementById("validationMessage");validationMessage.innerHTML = "Catalog Id is Valid";
}
else
{var validationMessage = document.getElementById("validationMessage");validationMessage.innerHTML = "Catalog Id is not Valid";
}
六、兼容性问题
window.ActiveXObject 对象
创建XMLHttpRequest 对象,必须考虑到浏览器兼容问题,有时可以利用判断浏览器是否支持ActiveX控件,如果浏览器支持ActiveX控件可以:
- var xml=new ActiveXObject("Microsoft.XMLHTTP");创建XMLHttpRequest 对象(IE7以前的版本中);
- var xml=new ActiveXObject("Msxml2.XMLHTTP");在较新的IE版本中创建XMLHttpRequest对象;
- var xml=new XMLHttpRequest();而在IE7以后及非IE浏览器中创建XMLHttpRequest对象。
首先我们来看看怎么来声明(使用)它,在使用XMLHTTPRequest对象发送请求和处理响应之前,我们必须要用javascript创建一个XMLHTTPRequest对象。(IE把XMLHTTPRequest实现为一个ActiveX对象,其他的浏览器[如Firefox/Safari/Opear]则把它实现为一个本地的javascript对象)。下面我们就来看看具体怎么运用javascript来创建它吧:
<script language="javascript" type="text/javascript">
var xmlhttp;
// 创建XMLHTTPRequest对象
function createXMLHTTPRequest(){if(window.ActiveXObject){ // 判断是否支持ActiveX控件xmlhttp = new ActiveObject("Microsoft.XMLHTTP"); // 通过实例化ActiveXObject的一个新实例来创建XMLHTTPRequest对象}else if(window.XMLHTTPRequest){ // 判断是否把XMLHTTPRequest实现为一个本地javascript对象xmlhttp = new XMLHTTPRequest(); // 创建XMLHTTPRequest的一个实例(本地javascript对象)}
}
</script>
js用来区别IE与其他浏览器及IE6-8之间的方法。
- document.all
- !!window.ActiveXObject;
使用方法如下:
if (document.all){alert(”IE浏览器”);
}else{alert(”非IE浏览器”);
}
if (!!window.ActiveXObject){alert(”IE浏览器”);
}else{alert(”非IE浏览器”);
}
区别IE6、IE7、IE8之间的方法:
var isIE=!!window.ActiveXObject;
var isIE6=isIE&&!window.XMLHttpRequest;
var isIE8=isIE&&!!document.documentMode;
var isIE7=isIE&&!isIE6&&!isIE8;
if (isIE){if (isIE6){alert(”ie6″);}else if (isIE8){alert(”ie8″);}else if (isIE7){alert(”ie7″);}
}
据说火狐以后也会加入document.all这个方法,所以建议使用第二种方法,应该会安全一些。
七、 小结
上面就是XMLHttpRequest对象使用的所有细节实现。通过不必把Web页面寄送到服务器而实现数据传送,XMLHttpRequest对象为客户端与服务器之间提供了一种动态的交互能力。你可以使用JavaScript启动一个请求并处理相应的返回值,然后使用浏览器的DOM方法更新页面中的数据。
附:简单的示例代码
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server"> <meta http-equiv="Content-Type" content="text/html;charset=gb2312" /> <title>示例</title> <script src="Ajax.js"></script>
</head>
<body>
<a href="#" onclick="ajaxReader('data5.xml'); return false"> 查看</a>
<div id="dataArea">
</div>
</body>
</html>
script:
// JScript File
function ajaxReader(file) {//定义xmlobj对象 var xmlObj=null; if(window.XMLHttpRequest) // 用 XMLHttpRequest 方式{xmlObj=new XMLHttpRequest(); }else if(window.ActiveXObject) // 用 ActiveXObject 方式{xmlObj=new ActiveXObject("Microsoft.XMLHTTP"); }else {return; }// 第一种方式xmlObj.onreadystatechange=function(){ // 值发生变化时触发if(xmlObj.readyState==4) { // 判断processXML(xmlObj.responseXML); }} xmlObj.open('GET',file,true); // 发送请求xmlObj.send(''); // send 发送 <!--// 第二种方式,下载docx// IE 版本xmlObj.open('GET',url,true);xmlObj.responseType = 'blob'xmlObj.setRequestHeader("Authorization", 'application/vnd.openxmlformats-officedocument.wordprocessingml.document')//post请求一定要添加请求头才行不然会报错if(model.type=='post'){xhr.setRequestHeader("Content-type","application/json");}xmlObj.onload = function(){if (this.status==200){var ret_data = this.response // this.response也就是请求的返回就是Blob对象var blob = new Blob([res.data.fileData],{type: 'application/json;charset=utf-8'}); //application/vnd.openxmlformats-officedocument.wordprocessingml.document这里表示doc类型womdow.navigator.msSaveBlob(blob,data['file_name'])}xmlObj.send(''); }// 谷歌浏览器 创建a标签 添加download属性下载var downloadElement = document.createElement('a');downloadElement.href = href;downloadElement.target = '_blank';downloadElement.download = 'model.json'; //下载后文件名document.body.appendChild(downloadElement);downloadElement.click(); //点击下载document.body.removeChild(downloadElement); //下载完成移除元素window.URL.revokeObjectURL(href); //释放掉blob对象-->}
function processXML(obj)
{var dataArray=obj.getElementsByTagName('pets')[0].childNodes;var dataArrayLength=dataArray.length;var insertData='<table>';insertData +='<tr><th>Pet5</th><th>Tasks</th></tr>'; for(var i=0;i<dataArrayLength;i++) { if(dataArray[i].tagName) { insertData+='<tr>'; insertData +='<td>'+dataArray[i].tagName+'</td>'; insertData +="<td>"+dataArray[i].getAttribute('tasks')+'</td>'; insertData+='</tr>'; } }insertData +=+'</table>'; document.getElementById('dataArea4').innerHTML =insertData;
}