得到请求对象之后就可以进入请求/响应循环了。记住,XMLHttpRequest
惟一的目的是让您发送请求和接收响应。其他一切都是 JavaScript、CSS 或页面中其他代码的工作:改变用户界面、切换图像、解释服务器返回的数据。准备好 XMLHttpRequest
之后,就可以向服务器发送请求了。
Ajax 采用一种沙箱安全模型。因此,Ajax 代码(具体来说就是 XMLHttpRequest
对象)只能对所在的同一个域发送请求。以后的文章中将进一步介绍安全和 Ajax,现在只要知道在本地机器上运行的代码只能对本地机器上的服务器端脚本发送请求。如果让 Ajax 代码在 www.breakneckpizza.com 上运行,则必须 www.breakneck.com 中运行的脚本发送请求。
首先要确定连接的服务器的 URL。这并不是 Ajax 的特殊要求,但仍然是建立连接所必需的,显然现在您应该知道如何构造 URL 了。多数应用程序中都会结合一些静态数据和用户处理的表单中的数据来构造该 URL。比如,清单 7 中的 JavaScript 代码获取电话号码字段的值并用其构造 URL。
<script language="javascript" type="text/javascript">
var request = false;
try {
request = new XMLHttpRequest();
} catch (trymicrosoft) {
try {
request = new ActiveXObject("Msxml2.XMLHTTP");
} catch (othermicrosoft) {
try {
request = new ActiveXObject("Microsoft.XMLHTTP");
} catch (failed) {
request = false;
}
}
}
if (!request)
alert("Error initializing XMLHttpRequest!");
function getCustomerInfo() {
var phone = document.getElementById("phone").value;
var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
}
</script>
这里没有难懂的地方。首先,代码创建了一个新变量 phone
,并把 ID 为 “phone” 的表单字段的值赋给它。清单 8 展示了这个表单的 XHTML,其中可以看到 phone
字段及其 id
属性。
清单 8. Break Neck Pizza 表单
<body>
<p><img src="breakneck-logo_4c.gif" alt="Break Neck Pizza" /></p>
<form action="POST">
<p>Enter your phone number:
<input type="text" size="14" name="phone" id="phone"
onChange="getCustomerInfo();" />
</p>
<p>Your order will be delivered to:</p>
<div id="address"></div>
<p>Type your order in here:</p>
<p><textarea name="order" rows="6" cols="50" id="order"></textarea></p>
<p><input type="submit" value="Order Pizza" id="submit" /></p>
</form>
</body>
还要注意,当用户输入电话号码或者改变电话号码时,将触发 清单 8 所示的 getCustomerInfo()
方法。该方法取得电话号码并构造存储在 url
变量中的 URL 字符串。记住,由于 Ajax 代码是沙箱型的,因而只能连接到同一个域,实际上 URL 中不需要域名。该例中的脚本名为 /cgi-local/lookupCustomer.php
。最后,电话号码作为 GET 参数附加到该脚本中:"phone=" + escape(phone)
。
如果以前没用见过 escape()
方法,它用于转义不能用明文正确发送的任何字符。比如,电话号码中的空格将被转换成字符 %20
,从而能够在 URL 中传递这些字符。
可以根据需要添加任意多个参数。比如,如果需要增加另一个参数,只需要将其附加到 URL 中并用 “与”(&
)字符分开 [第一个参数用问号(?
)和脚本名分开]。
|
有了要连接的 URL 后就可以配置请求了。可以用 XMLHttpRequest
对象的 open()
方法来完成。该方法有五个参数:
- request-type:发送请求的类型。典型的值是
GET
或POST
,但也可以发送HEAD
请求。 - url:要连接的 URL。
- asynch:如果希望使用异步连接则为 true,否则为 false。该参数是可选的,默认为 true。
- username:如果需要身份验证,则可以在此指定用户名。该可选参数没有默认值。
- password:如果需要身份验证,则可以在此指定口令。该可选参数没有默认值。
通常使用其中的前三个参数。事实上,即使需要异步连接,也应该指定第三个参数为 “true”。这是默认值,但坚持明确指定请求是异步的还是同步的更容易理解。
将这些结合起来,通常会得到 清单 9 所示的一行代码。
function getCustomerInfo() {
var phone = document.getElementById("phone").value;
var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
request.open("GET", url, true);
本系列的后面一篇文章中,我将用很多时间编写和使用异步代码,但是您应该明白为什么 open()
的最后一个参数这么重要。在一般的请求/响应模型中,比如 Web 1.0,客户机(浏览器或者本地机器上运行的代码)向服务器发出请求。该请求是同步的,换句话说,客户机等待服务器的响应。当客户机等待的时候,至少会用某种形式通知您在等待:
- 沙漏(特别是 Windows 上)。
- 旋转的皮球(通常在 Mac 机器上)。
- 应用程序基本上冻结了,然后过一段时间光标变化了。
这正是 Web 应用程序让人感到笨拙或缓慢的原因 —— 缺乏真正的交互性。按下按钮时,应用程序实际上变得不能使用,直到刚刚触发的请求得到响应。如果请求需要大量服务器处理,那么等待的时间可能很长(至少在这个多处理器、DSL 没有等待的世界中是如此)。
而异步请求不 等待服务器响应。发送请求后应用程序继续运行。用户仍然可以在 Web 表单中输入数据,甚至离开表单。没有旋转的皮球或者沙漏,应用程序也没有明显的冻结。服务器悄悄地响应请求,完成后告诉原来的请求者工作已经结束(具体的办法很快就会看到)。结果是,应用程序感觉不 那么迟钝或者缓慢,而是响应迅速、交互性强,感觉快多了。这仅仅是 Web 2.0 的一部分,但它是很重要的一部分。所有老套的 GUI 组件和 Web 设计范型都不能克服缓慢、同步的请求/响应模型。
一旦用 open()
配置好之后,就可以发送请求了。幸运的是,发送请求的方法的名称要比 open()
适当,它就是 send()
。
send()
只有一个参数,就是要发送的内容。但是在考虑这个方法之前,回想一下前面已经通过 URL 本身发送过数据了:
var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
|
虽然可以使用 send()
发送数据,但也能通过 URL 本身发送数据。事实上,GET
请求(在典型的 Ajax 应用中大约占 80%)中,用 URL 发送数据要容易得多。如果需要发送安全信息或 XML,可能要考虑使用 send()
发送内容(本系列的后续文章中将讨论安全数据和 XML 消息)。如果不需要通过 send()
传递数据,则只要传递 null
作为该方法的参数即可。因此您会发现在本文中的例子中只需要这样发送请求(参见 清单 10)。
function getCustomerInfo() {
var phone = document.getElementById("phone").value;
var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
request.open("GET", url, true);
request.send(null);
}
现在我们所做的只有很少一点是新的、革命性的或异步的。必须承认,open()
方法中 “true” 这个小小的关键字建立了异步请求。但是除此之外,这些代码与用 Java servlet 及 JSP、PHP 或 Perl 编程没有什么两样。那么 Ajax 和 Web 2.0 最大的秘密是什么呢?秘密就在于 XMLHttpRequest
的一个简单属性 onreadystatechange
。
首先一定要理解这些代码中的流程(如果需要请回顾 清单 10)。建立其请求然后发出请求。此外,因为是异步请求,所以 JavaScript 方法(例子中的 getCustomerInfo()
)不会等待服务器。因此代码将继续执行,就是说,将退出该方法而把控制返回给表单。用户可以继续输入信息,应用程序不会等待服务器。
这就提出了一个有趣的问题:服务器完成了请求之后会发生什么?答案是什么也不发生,至少对现在的代码而言如此!显然这样不行,因此服务器在完成通过 XMLHttpRequest
发送给它的请求处理之后需要某种指示说明怎么做。
|
现在 onreadystatechange
属性该登场了。该属性允许指定一个回调函数。回调允许服务器(猜得到吗?)反向调用 Web 页面中的代码。它也给了服务器一定程度的控制权,当服务器完成请求之后,会查看 XMLHttpRequest
对象,特别是 onreadystatechange
属性。然后调用该属性指定的任何方法。之所以称为回调是因为服务器向网页发起调用,无论网页本身在做什么。比方说,可能在用户坐在椅子上手没有碰键盘的时候调用该方法,但是也可能在用户输入、移动鼠标、滚动屏幕或者点击按钮时调用该方法。它并不关心用户在做什么。
这就是称之为异步的原因:用户在一层上操作表单,而在另一层上服务器响应请求并触发 onreadystatechange
属性指定的回调方法。因此需要像 清单 11 一样在代码中指定该方法。
function getCustomerInfo() {
var phone = document.getElementById("phone").value;
var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
request.open("GET", url, true);
request.onreadystatechange = updatePage;
request.send(null);
}
需要特别注意的是该属性在代码中设置的位置 —— 它是在调用 send()
之前 设置的。发送请求之前必须设置该属性,这样服务器在回答完成请求之后才能查看该属性。现在剩下的就只有编写 updatePage()
方法了,这是本文最后一节要讨论的重点。