Javascript 基础---Ajax入门必看
ajax 是现代web 应用程序开发的一项关键工具。它让你能向服务器异步发送和接收数据,然后用 javascript 解析。 ajax 是 asynchronous javascript and xml (异步javascript 与xml)的缩写。
ajax 核心规范的名称继承于用来建立和发起请求的 javascript 对象:xmlhttprequest 。这个规范有两个等级。所有主流浏览器都实现了第一级,它代表了基础级别的功能。第二级扩展了最初的规范,纳入了额外的事件和一些功能来让它更容易与 form 元素协作,并且支持一些相关规范。
1. ajax起步
ajax 的关键在于 xmlhttprequest 对象,而理解这个对象的方法是看个例子。下面代码展示了 xmlhttprequest 对象的简单用法:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>example</title> </head> <body> <div> <button>apples</button> <button>cherries</button> <button>bananas</button> </div> <div id="target"> press a button </div> <script type="application/javascript"> var buttons = document.getelementsbytagname("button"); for(var i=0; i<buttons.length; i++){ buttons[i].onclick = handlebuttonpress; } //脚本会调用此函数以响应 button 控件的 click 事件 function handlebuttonpress(e){ //创建一个新的 xmlhttprequest 对象 var httprequest = new xmlhttprequest(); //给 onreadystatechange 事件设置一个事件处理器 httprequest.onreadystatechange = handleresponse; //使用 open 方法来指定 http 方法和需要请求的 url (即告诉 httprequest 对象你想要做的事) httprequest.open("get", e.target.innerhtml+".html"); //这里没有向服务器发送任何数据,所以 send 方法无参数可用 httprequest.send(); } //处理响应 //一旦脚本调用了 send 方法,浏览器就会在后台发送请求到服务器。因为请求是在后台处理的,所以ajax 依靠事件来通知这个请求的进展情况。 function handleresponse(e){ //当 onreadystatechange 事件被触发后,浏览器会把一个 event 对象传递给指定的处理函数,target 属性则会被设为与此事件关联的xmlhttprequest if(e.target.readystate == xmlhttprequest.done && e.target.status == 200){ //请求成功 document.getelementbyid("target").innerhtml = e.target.responsetext; //显示被请求文档的内容 } } </script> </body> </html>
三个额外的文档非常简单:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>apples</title> <style> img { float:left;padding:2px;margin:5px;border: medium double black;background-color: lightgrey; width: 100px;height: 100px;} </style> </head> <body> <p> <img src="../img/show-page/img_apples.jpg"/> page for apples. </p> </body> </html>
效果如下图所示:
随着用户点击各个水果按钮,浏览器会异步执行并取回所请求的文档,而主文档不会被重新加载。这就是典型的 ajax 行为。
2. 使用 ajax 事件
建立和探索一个简单的示例之后,可以开始深入了解 xmlhttprequest 对象支持的功能,以及如何在请求中使用它们了。起点就是第二级规范里定义的那些额外事件:
这些事件大多数会在请求的某一特定时间点上触发。 readystatechange 和 progress 这两个事件是例外,它们可以多次触发以提供进度更新。
调度这些事件时,浏览器会对 readystatechange 事件使用常规的 event 对象,对其他事件则使用 progressevent 对象。 progressevent 对象定义了 event 对象的所有成员,并增加了下图中介绍的这些成员:
下面代码展示了如何使用这些事件:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>example</title> <style> table {margin: 10px;border-collapse: collapse; float: left;} div{margin: 10px;} td,th{padding: 4px;} </style> </head> <body> <div> <button>apples</button> <button>cherries</button> <button>bananas</button> </div> <table id="events" border="1"></table> <div id="target"> press a button </div> <script type="application/javascript"> var buttons = document.getelementsbytagname("button"); for(var i=0; i<buttons.length; i++){ buttons[i].onclick = handlebuttonpress; } var httprequest; function handlebuttonpress(e){ cleareventdetails(); httprequest = new xmlhttprequest(); httprequest.onreadystatechange = handleresponse; httprequest.onerror = handleerror; httprequest.onload = handleload;; httprequest.onloadend = handleloadend; httprequest.onloadstart = handleloadstart;; httprequest.onprogress = handleprogress; httprequest.open("get", e.target.innerhtml+".html"); httprequest.send(); } function handleresponse(e){ displayeventdetails("readystate("+httprequest.readystate+")") if(e.target.readystate == xmlhttprequest.done && e.target.status == 200){ document.getelementbyid("target").innerhtml = e.target.responsetext; } } function handleerror(e){ displayeventdetails("error",e);} function handleload(e){ displayeventdetails("load",e);} function handleloadend(e){ displayeventdetails("loadend",e);} function handleloadstart(e){ displayeventdetails("loadstart",e);} function handleprogress(e){ displayeventdetails("progress",e);} function cleareventdetails(){ document.getelementbyid("events").innerhtml = "<tr><th>event</th><th>lengthcomputable</th><th>loaded</th><th>total</th>"; } function displayeventdetails(eventname,e){ if(e){ document.getelementbyid("events").innerhtml +="<tr><td>"+eventname+"</td><td>"+ e.lengthcomputable+"</td><td>"+ e.loaded+"</td><td>"+ e.total+"</td></tr>"; }else { document.getelementbyid("events").innerhtml += "<tr><td>"+eventname+"</td><td>na</td><td>na</td><td>na</td></tr>"; } } </script> </body> </html>
这是之前示例的一种变型,为一些事件注册了处理函数,并在一个 table 元素里为处理的每个事件都创建了一条记录。从下图中可以看到 firefox 浏览器是如何触发这些事件的。
3. 处理错误
使用 ajax 时必须留心两类错误。它们之间的区别源于视角不同。
第一类错误是从 xmlhttprequest 对象的角度看到的问题:某些因素阻止了请求发送到服务器。例如 dns 无法解析主机名,连接请求被拒绝,或者url无效。
第二类问题是从应用程序的角度看到的问题,而非 xmlhttprequest 对象。它们发生于请求成功发送至服务器,服务器接收请求、进行处理并生成响应,但该响应并不指向你期望的内容。例如,如果请求的url 不存在,这类问题就会发生。
有三种方式可以处理这些错误,如下面代码所示:
3.1 处理设置错误
需要处理的第一类问题是向 xmlhttpresquest 对象传递了错误的数据,比如格式不正确的 url 。它们极其容易发生在生成基于用户输入的url 时。为了模拟这类问题,上面文档中有添加一个标签 bad url (错误的url)的button 。按下这个按钮会以以下形式调用 open 方法:
httprequest.open("get","http://")
这是一种会阻止请求执行的错误,而 xmlhttprequest 对象会发生这类事件时抛出一个错误。这就意味着需要用一条 try...catch 语句来围住设置请求的代码,就像这样:
try{ ... httprequest.open("get","http://") ... httprequest.send(); }catch(error){ displayerrormsg("try/catch",error.message) }
catch 子句让你有机会从错误中恢复。可以选择提示用户输入一个值,也可以回退至默认的url ,或是简单地丢弃这个请求。 在这个例子中,仅仅调用了 displayerrormsg 函数来显示错误消息。
3.2 处理请求错误
第二类错误发生在请求已生成,但其他方面出错时。为了模拟这类问题,在示例中添加了一个标签为 bad host (错误主机)的按钮。当这个按钮被按下后,就会调用 open 方法访问一个不可用的 url:
httprequest.open("get",http://www.ycdoitt.com/nopage.html)
这个url 存在两个问题。第一个问题是主机名不能被 dns 解析,因此浏览器无法生成服务器连接。这个问题知道 xmlhttprequest 对象开始生成请求时才会变得明显,因此它会以两种方式发出错误信号。如果你注册了一个 error 事件的监听器,浏览器就会向你的监听函数发送一个 event 对象。以下是示例中使用的函数:
function handleerror(e){ displayerrormsg("error event",httprequest.status + httprequest.statustext); }
当这类错误发生时,能从 xmlhttprequest 对象获得何种程度的信息取决于浏览器,遗憾的是大多数情况下,会得到的值为 0的 status和空白的 statustext 值。
第二个问题是url和生成请求的具有不同的来源,在默认情况下这是不允许的。你通常只能向载入脚本的同源url发送ajax请求。浏览器报告这个问题时可能会抛出 error 或者触发error事件,不同浏览器的处理方法不尽相同。不同浏览器还会在不同的时点检查来源,这就意味着不一定总是能看到浏览器对同一问题突出显示。可以使用跨站资源规范(cors,cross-origin resource sharing)来绕过同源限制。
3.3 处理应用程序错误
最后一类错误发生于请求成功完成(从xmlhttprequest对象的角度看),但没有返回你想要的数据时。为了制造这类问题,在上面示例中添加一个说明标签为 cucumber 的 button 。按下这个按钮会生成类似于 apples、cherries 和 bananas 按钮那样的请求url,但是在服务器上不存在 cucumber.html 这个文档。
这一过程本身没有错误(因为请求已完成),需要根据 status属性来确定发生了什么。当请求某个存在的文档时,会获得404这个状态码,它的意思是服务器无法找到请求的文档。可以看到示例是如何处理200(意思是ok)以外的状态码的:
if(httprequest.status == 200){ target.innerhtml = httprequest.responsetext; }else{ document.getelementbyid("statusmsg").innerhtml = "status:" + httprequest.status +" "+ httprequest.statustext; }
在这个例子中,只是简单的显示了status和statustext的值。而在真正的应用程序里,需要以一种有用且有意义的方式进行恢复(比如显示备用内容或警告用户有问题,具体看哪种更适合应用程序)。
4. 获取和设置标头
使用xmlhttprequest对象,可以设置发送给服务器的请求标头(header)和读取服务器响应里的标头。
4.1 覆盖请求的http方法
通常不需要添加或修改ajax请求里的标头。浏览器知道需要发送些什么,服务器也知道如何进行响应。不过,有几种情况例外。第一种是 x-http-method-override 标头。
http标准通常被用于在互联网上请求和传输html文档,它定义了许多方法。大多数人都知道get和post,因为它们的使用最为广泛。不过还存在其他一些方法(包括put和delete),这些http方法用来给向服务器请求的url赋予意义,而且这种用法正在呈现上升趋势。举个例子,假如想查看某条用户记录,可以生成这样一个请求:
httprequest.open("get","http://myserver/records/freeman/adam");
这里只展示了http方法和请求的url。要使这个请求能顺利工作,服务器端必须由应用程序能理解这个请求,并将它转变成一段合适的数据以发送回服务器。如果想删除数据,可以这么写:
httprequest.open("delete","http://myserver/records/freeman/adam");
此处的关键在于通过http方法表达出你想让服务器做什么,而不是把它用某种方式编码进url。
以这种方式使用http方法的问题在于:许多主流的web技术只支持get和post,而且不少防火墙只允许get和post请求通过。有一种惯用的做法可以规避这个限制,就是使用 x-http-method-override标头来指定想要使用的http方法,但形式上市在发送一个post请求。代码演示如下:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>example</title> </head> <body> <div> <button>apples</button> <button>cherries</button> <button>bananas</button> </div> <div id="target">press a button</div> <script> var buttons = document.getelementsbytagname("button"); for(var i = 0; i < buttons.length; i++){ buttons[i].onclick = handlebuttonpress; } var httprequest; function handlebuttonpress(e){ httprequest = new xmlhttprequest(); httprequest.onreadystatechange = handleresponse; httprequest.open("get", e.target.innerhtml+".html"); httprequest.setrequestheader("x-http-method-override","delete"); httprequest.send(); } function handleerror(e){ displayerrormsg("error event",httprequest.status+httprequest.statustext); } function handleresponse(){ if(httprequest.readystate == 4 && httprequest.status == 200){ document.getelementbyid("target").innerhtml = httprequest.responsetext; } } </script> </body> </html>
在这个例子中,有使用xmlhttprequest对象上的setrequestheader方法来表明想让这个请求以http delete方法的形式进行处理。请注意我在调用open方法之后才设置了这个标头。如果试图在open方法之前使用setrequestheader方法,xmlhttprequest对象就会抛出一个错误。
ps:覆盖http需要服务器端的web应用程序框架能理解x-http-method-override这个惯例,并且你的服务器端应用程序要设置成能寻找和理解那些用的较少的http方法。
4.2 禁用内容缓存
第二个可以添加到ajax请求上的有用标头是cache-control,它在编写和调试脚本时尤其有用。一些浏览器会缓存通过ajax请求所获得的内容,在浏览会话期间不会再请求它。对在前面的例子而言,意味着 apples.html、cherries.html和bananas.html 上的改动不会立即反映到浏览器中。下面代码展示了可以如何设置标头来避免这一点:
httprequest = new xmlhttprequest(); httprequest.onreadystatechange = handleresponse; httprequest.open("get", e.target.innerhtml+".html"); httprequest.setrequestheader("cache-control","no-cache"); httprequest.send();
设置标头的方式和之前的例子一样,但这次用到的标头是 cache-control,而想要的值是 no-cache。放置这条语句后,如果通过ajax请求的内容发生了改变,就会在下一次请求文档时体现出来。
4.3 读取响应标头
可以通过 getresponseheader 和 getallresponseheaders 方法来读取服务器响应某个ajax请求时发送的http标头。在大多数情况下,你不需要关心标头里有什么,因为它们是浏览器和服务器之间交互事务的组成部分。下面代码展示了如何使用这个属性:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta content="width=device-width,user-scalable=no" name="viewport" /> <meta name="author" content="叶超luka" /> <meta name="description" content="a simple example" /> <title>example</title> <link href="../img/ycdoit.ico" type="image/x-icon" rel="shortcut icon" /> <style> #allheaders,#ctheader{border: medium solid black;padding: 2px;margin: 2px;} </style> </head> <body> <div> <button>apples</button> <button>cherries</button> <button>bananas</button> </div> <div id="ctheader"></div> <div id="allheaders"></div> <div id="target">press a button</div> <script> var buttons = document.getelementsbytagname("button"); for(var i = 0; i < buttons.length; i++){ buttons[i].onclick = handlebuttonpress; } var httprequest; function handlebuttonpress(e){ httprequest = new xmlhttprequest(); httprequest.onreadystatechange = handleresponse; httprequest.open("get", e.target.innerhtml+".html"); httprequest.setrequestheader("cache-control","no-cache"); httprequest.send(); } function handleresponse(){ if(httprequest.readystate==2){ document.getelementbyid("allheaders").innerhtml = httprequest.getallresponseheaders(); document.getelementbyid("ctheader").innerhtml = httprequest.getresponseheader("content-type"); }else if(httprequest.readystate == 4 && httprequest.status == 200){ document.getelementbyid("target").innerhtml = httprequest.responsetext; } } </script> </body> </html>
效果图如下:
根据此图可以看出开发服务器正在运行的web服务器软件是 intellij idea 15.0.4,最后修改 apples.html 文档的时间是6月27日(但屏幕截图是7月5日)。
上一篇: 详解node中创建服务进程