一. 什么是AJAX?
这个名字代表了异步JavaScript+XMLHTTPRequest,并且意味着你可以在基于浏览器的JavaScript和服务器之间建立套接字通讯。其实AJAX并不是一种新技术,而是已经成功地用于现代浏览器中的若干成功技术的可能性组合。所有的AJAX应用程序实现了一种“丰富的”UI——这是通过JavaScript操作HTML文档对象模型并且经由XMLHttpRequest实现的精确定位的数据检索来实现的。典型的示例AJAX应用程序是Google Labs(http://labs.google.com)的Google Maps和Google Suggest。这些应用程序现场监视用户输入并且提供实时的页面更新。最重要的是,在用户通过地图导航或输入一个查找字符串的同时,这些事件不需要刷新页面。
事实上,支持这些令人感到惊讶的应用的技术已经出现一段时间了,尽管它们要求复杂的技能以及使用浏览器的技巧。一些专利产品就提供了相似的能力——如Macromedia Flash插件,Java Applets或.NET运行时——在达到实用上已经有一段时间了。把一种可与服务器通话的脚本组件引入到浏览器中的思想早在IE 5.0中就已经存在。Firefox和其它流行的浏览器也加入到浏览器大军中并以一种内置对象形式支持XMLHTTPRequest。随着跨平台浏览器的出现,这些技术得到了认可并在2004年3月一家称为Adaptive Path的公司中正式提出了AJAX。
简而言之,由于来自于Google的支持和安装了一点可用的浏览器技术,加上为了一种"更好的用户体验",每个人都在把客户端技术添加到Web应用程序上。
二. AJAX与传统应用程序的区别
一个传统Web应用程序模型实际上是一种基本的事件——用户被迫提交表单以实现页面交换。也就是说,表单提交和页面传送无法得到保证:还有更坏的情形——用户需要再次点击。这与AJAX截然不同-——数据跨过线路而不是完整的HTML页面传输。这种数据交换是经由特定的浏览器对象:XMLHttpRequest实现的;再由适当的逻辑来处理每个数据请求的结果,页面的特定区域而不是完整的页面被更新。结果是更快的速度,更少的拥挤和更好的信息传送控制。
传统型"click-refresh"Web应用程序强迫用户中断工作过程而等待页面的重装。通过引入AJAX技术,一个客户端脚本能够异步地与服务器通话,而用户仍能保持输入数据。除了对用户透明之外,这样的异步意味着服务器可以有更多时间来处理请求。
传统Web应用程序把所有的处理代理到服务器并且强迫服务器进行状态管理。AJAX允许灵活划分应用程序逻辑以及客户和服务器之间的状态管理。这就消除了一种"click-refresh"依赖性并且提供更好的服务器可伸缩性。当该状态存储在客户端,你就不必跨越服务器来维持会话或保存/结束状态-其使用期限是由客户端来定义的。
三. AJAX——分布式的MVC
尽管AJAX应用程序依靠JavaScript来实现描述层,然而处理能力和知识库仍然存在于服务器上。此时,AJAX应用程序大量的与J2EE服务器通讯——把数据输入/输出Web服务和servlets。具有基于AJAX的描述层的J2EE应用程序和标准J2EE应用程序之间的区别首先在于,MVC是通过线路分布的。通过使用AJAX,视图是本地的,而模型和控制器是分布式的——这使得开发者能够灵活地决定哪些部件会是基于客户端的。具体地说,本地视图通过巧妙地操作HTML DOM而生成图形;控制器局部地处理用户输入并且根据开发者的判断扩展到服务器的处理——经由HTTP请求(Web服务,XML/RPC或其它)实现;模型的远程部分是根据客户端需要而下载的以达到实时更新客户端页面;并且状态是在客户端收集的。
在以后的AJAX文章中,我们将比较深入地讨论这里的每一种组件并提供有关它们联合在一起进行应用的示例。现在,先不多说,让我们详细地分析一个简单的AJAX示例。
四. 邮政区号校验和查询
我们将创建一个包含三个INPUT字段(Zip,City和State)的HTML页面。我们将保证,只要用户输入邮政区号的前三个数字,该页面上的字段就会用第一个匹配的状态值填充。一旦用户输入了所有五位邮政区号数,我们将立即决定和填充相应的城市。如果邮政区号无效(在服务器的数据库没有找到),那么我们将把邮政区号的边界设置为红色。这样的可视化线索有助于用户并且在现代浏览器中已经成为一种标准(作为一实例,当Firefox找到一个HTML页面中的匹配关键字时,它会高亮与你在浏览器查找域输入的内容一致的部分)。
让我们首先创建一个简单的包含三个INPUT字段的HTML:zip,city和state。请注意,一旦一个字符输入进邮政区号字段域中,即调用方法zipChanged()。JavaScript函数zipChanged()(见下)在当zip长度为3时调用函数updateState(),而在当zip长度为5时调用函数up-dateCity()。而updateCity()和updateState()把大部分的工作代理到另一个函数ask()。
Zip:<input id="zipcode" type="text" maxlength="5" onKeyUp="zipChanged()" style="width:60"/> City: <input id="city" disabled maxlength="32"/> State:<input id="state" disabled maxlength="2"/> <script src="xmlhttp.js"></script> <script> var zipField = null; function zipChanged(){ zipField = document.getElementById("zipcode") var zip = zipField.value; zip.length == 3?updateState(zip):zip.length == 5?updateCity(zip):""; } function updateState(zip) { var stateField = document.getElementById("state"); ask("resolveZip.jsp?lookupType=state&zip="+zip, stateField, zipField); } function updateCity(zip) { var cityField = document.getElementById("city"); ask("resolveZip.jsp? lookupType=city&zip="+zip, cityField, zipField); } </script> |
函数ask()与服务器进行通讯并分配一个回调函数来处理服务器的响应(见下列代码)。后面,我们将分析具有双重特点的resolveZip.jsp的内容-它根据zip字段中的字符数查找city或state信息。重要的是,ask()使用了具有异步特点的XmlHttpRequest,这样填充state和city字段或着色zip字段边界就可以不必减慢数据入口而得以实现。首先,我们调用request.open()-它用服务器打开套接字频道,使用一个HTTP动词(GET或POST)作为第一个参数并且以数据提供者的URL作为第二个参数。request.open()的最后一个参数被设置为true-它指示该请求的异步特性。注意,该请求还没有被提交。随着对request.send()的调用,开始提交-这可以为POST提供任何必要的有效载荷。在使用异步请求时,我们必须使用request.onreadystatechanged属性来分配请求的回调函数。(如果请求是同步的话,我们应该能够在调用request.send之后立即处理结果,但是我们也有可能阻断用户,直到该请求完成为止。)
HTTPRequest = function () { var xmlhttp=null; try { xmlhttp = new ActiveXObject("Msxml2.XMLHTTP"); try { xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); } catch (_E) { } try { xmlhttp = new XMLHttpRequest(); } catch (e) { xmlhttp = false; } function ask(url, fieldToFill, lookupField) { function handleHttpResponse(http, fieldToFill, lookupField) { result = http.responseText; if ( -1 != result.search("null") ) { lookupField.style.borderColor = "red"; fieldToFill.value = ""; } else { lookupField.style.borderColor = ""; fieldToFill.value = result; } |
为ask()所使用的HttpRequest()函数(见上)是一跨浏览器的XMLHTTPRequest的一个实例的构造器;稍后我们将分析它。到目前为止,请注意对于handleResponse()的调用是如何用一匿名函数包装的-这个函数是function(){handleHttpResponse(http,fieldToFill, lookupField)}。
该函数的代码是动态创建的并且在每次我们给http.onreadstatechange属性赋值时被编译。结果,JavaScript创建一个指向上下文(所有的变量都可以存取正在结束的方法-ask())的指针。这样以来,匿名函数和handleResponse()就能够被保证充分存取所有的上下文宿主的变量,直至到匿名函数的参考被垃圾回收站收集为止。换句话说,无论何时我们的匿名函数被调用,它都能无缝地参考request,fieldToFill和lookupField变量,就象它们是全局的一样。而且,每次ask()调用都将创建环境的一个独立拷贝,并且此时这些变量中保存有该函数将结束时的值。
现在,让我们分析一下函数handleResponse()。既然它能够在请求处理的不同状态下激活,那么该函数将忽略所有的情形-除了该请求处理完成之外-这相应于request.readyState属性等于4("Completed")。此时,该函数读取服务器的响应文本。与它的名字所暗示的相反,XmlHttpRequest的输入和输出都不必限于XML格式。特别地,我们的resolveZip.jsp(见源码中的列表1)返回普通文本。如果返回值为"unknown",那么该函数将假定邮政区号是无效的并且把查找字段(zip)边界颜色置为红色。否则,返回值被用于填充字段state或city,并且zip的边界被赋予一种缺省颜色。
XMLHttpRequest-传输对象
让我们返回到我们的XMLHTTPRequest的跨浏览器实现。最后一个列表包含一个HttpRequest()函数-它向上兼容于IE5.0和Mozilla 1.8/FireFox。为简化起见,我们只创建一个微软XMLHTTPRequest对象,而且如果创建失败,我们假定它是Firefox/Mozilla。
该函数的核心是XMLHTTPRequest-这是一个本机浏览器对象,它为包括HTTP协议的任何东西与服务器之间的通讯提供方便。它允许指定任何HTTP动词,头部和有效载荷,并且能够以异步或同步方式工作。不需要下载也不需要安装任何插件-尽管在IE的情形下,XMLHTTPRequest是一个集成到浏览器内部的ActiveX。因而,"Run ActiveX Control and Plugins"默认IE权限应该正好适合使用它。
最重要的是,XMLHTTPRequest允许一个到服务器的RPC风格的编程查询而不需要任何页面刷新。它以一种可预测的,可控制的方式来实现此-提供了到HTTP协议的所有细节的完整存取-包括头部和数据的任何定制格式。在以后的文章中,我们将向你展示其它一些业界协议-你可以在这些传输协议(如Web服务和XML-RPC)之上运行-它们极大地简化大规模应用程序的开发和维护。