Javascript 基础---Ajax入门必看


Posted in Javascript onJuly 06, 2016

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>

效果如下图所示:

Javascript 基础---Ajax入门必看

随着用户点击各个水果按钮,浏览器会异步执行并取回所请求的文档,而主文档不会被重新加载。这就是典型的 Ajax 行为。

 2. 使用 Ajax 事件

建立和探索一个简单的示例之后,可以开始深入了解 XMLHttpRequest 对象支持的功能,以及如何在请求中使用它们了。起点就是第二级规范里定义的那些额外事件:

Javascript 基础---Ajax入门必看

这些事件大多数会在请求的某一特定时间点上触发。 readystatechange 和 progress 这两个事件是例外,它们可以多次触发以提供进度更新。

调度这些事件时,浏览器会对 readystatechange 事件使用常规的 Event 对象,对其他事件则使用 ProgressEvent 对象。 ProgressEvent 对象定义了 Event 对象的所有成员,并增加了下图中介绍的这些成员:

Javascript 基础---Ajax入门必看

下面代码展示了如何使用这些事件:

<!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 浏览器是如何触发这些事件的。

Javascript 基础---Ajax入门必看

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)和读取服务器响应里的标头。

Javascript 基础---Ajax入门必看

 

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>

效果图如下:

Javascript 基础---Ajax入门必看

根据此图可以看出开发服务器正在运行的Web服务器软件是 IntelliJ IDEA 15.0.4,最后修改 apples.html 文档的时间是6月27日(但屏幕截图是7月5日)。

 

Javascript 相关文章推荐
JavaScript 关键字屏蔽实现函数
Aug 02 Javascript
Mootools 图片展示插件(lightbox,ImageMenu)收集集合
May 21 Javascript
如何使用Jquery获取Form表单中被选中的radio值
Aug 09 Javascript
jsonp原理及使用
Oct 28 Javascript
浅析jQuery1.8的几个小变化
Dec 10 Javascript
JavaScript 消息框效果【实现代码】
Apr 27 Javascript
jQuery设置Easyui校验规则(推荐)
Nov 21 Javascript
vue元素实现动画过渡效果
Jul 01 Javascript
dropload.js插件下拉刷新和上拉加载使用详解
Oct 20 Javascript
vue.js系列中的vue-fontawesome使用
Feb 10 Javascript
JS使用正则表达式获取小括号、中括号及花括号内容的方法示例
Jun 01 Javascript
如何基于layui的laytpl实现数据绑定的示例代码
Apr 10 Javascript
jQuery获取浏览器类型和版本号的方法
Jul 05 #Javascript
jQuery事件委托之Safari
Jul 05 #Javascript
一道优雅面试题分析js中fn()和return fn()的区别
Jul 05 #Javascript
JS实现环形进度条(从0到100%)效果
Jul 05 #Javascript
JQuery组件基于Bootstrap的DropDownList(完整版)
Jul 05 #Javascript
结合代码图文讲解JavaScript中的作用域与作用域链
Jul 05 #Javascript
jQuery的Each比JS原生for循环性能慢很多的原因
Jul 05 #Javascript
You might like
PHP has encountered an Access Violation
2007/01/15 PHP
php获取目录中所有文件名及判断文件与目录的简单方法
2017/03/04 PHP
thinkphp5.1框架容器与依赖注入实例分析
2019/07/23 PHP
js 兼容多浏览器的回车和鼠标焦点事件代码(IE6/7/8,firefox,chrome)
2010/04/14 Javascript
javascript中apply和call方法的作用及区别说明
2014/02/14 Javascript
详解Javascript动态操作CSS
2014/12/08 Javascript
js实现大转盘抽奖游戏实例
2015/06/24 Javascript
jQuery通过deferred对象管理ajax异步
2016/05/20 Javascript
JavaScript数组的栈方法与队列方法详解
2016/05/26 Javascript
jquery div模态窗口的简单实例
2016/05/28 Javascript
深入解析桶排序算法及Node.js上JavaScript的代码实现
2016/07/06 Javascript
AngularJS入门教程之服务(Service)
2016/07/27 Javascript
jQuery.Validate表单验证插件的使用示例详解
2017/01/04 Javascript
快速实现JS图片懒加载(可视区域加载)示例代码
2017/01/04 Javascript
jquery事件与绑定事件
2017/03/16 Javascript
微信小程序 首页制作简单实例
2017/04/07 Javascript
JavaScript正则表达式校验与递归函数实际应用实例解析
2017/08/04 Javascript
JavaScript 数组的进化与性能分析
2017/09/18 Javascript
JavaScript设计模式之缓存代理模式原理与简单用法示例
2018/08/07 Javascript
微信小程序 MinUI组件库系列之badge徽章组件示例
2018/08/20 Javascript
使用vue-cli3 创建vue项目并配置VS Code 自动代码格式化 vue语法高亮问题
2019/05/14 Javascript
Vue单页面应用中实现Markdown渲染
2021/02/14 Vue.js
用生成器来改写直接返回列表的函数方法
2017/05/25 Python
Python基于mysql实现学生管理系统
2019/02/21 Python
python异常触发及自定义异常类解析
2019/08/06 Python
pytorch加载自定义网络权重的实现
2020/01/07 Python
Pyecharts 中Geo函数常用参数的用法说明
2021/02/01 Python
pycharm进入时每次都是insert模式的解决方式
2021/02/05 Python
HTML5 Canvas 旋转风车绘制
2017/08/18 HTML / CSS
南京某公司笔试题
2013/01/27 面试题
国际贸易专业个人求职信范文分享
2013/12/14 职场文书
如何写一份好的英文求职信
2014/03/19 职场文书
小学课外活动总结
2014/07/09 职场文书
租房安全协议书
2014/08/20 职场文书
2014年前台文员工作总结
2014/12/08 职场文书
青年教师个人总结
2015/02/11 职场文书