?获取表单数据:JavaScript 代码很容易从 HTML 表单中抽取数据并发送到服务器。
?修改表单上的数据:更新表单也很简单,从设置字段值到迅速替换图像。
?解析 HTML 和 XML:使用 JavaScript 代码操纵 DOM(请参阅 下一节),处理 HTML 表单服务器返回的 XML 数据的结构。
对于前两点,需要非常熟悉 getElementById() 方法,如 清单 2 所示。
清单 2. 用 JavaScript 代码捕获和设置字段值
// Get the value of the "phone" field and stuff it in a variable called phone
var phone = document.getElementById("phone").value;
// Set some values on a form using an array called response
document.getElementById("order").value = response[0];
document.getElementById("address").value = response[1];
这里没有特别需要注意的地方,真是好极了!您应该认识到这里并没有非常复杂的东西。只要掌握了 XMLHttpRequest,Ajax 应用程序的其他部分就是如 清单 2 所示的简单 JavaScript 代码了,混合有少量的 HTML。同时,还要用一点儿 DOM,我们就来看看吧。
以 DOM 结束
最后还有 DOM,即文档对象模型。可能对有些读者来说 DOM 有点儿令人生畏,HTML 设计者很少使用它,即使 JavaScript 程序员也不大用到它,除非要完成某项高端编程任务。大量使用 DOM 的是 复杂的 Java 和 C/C++ 程序,这可能就是 DOM 被认为难以学习的原因。
幸运的是,在 JavaScript 技术中使用 DOM 很容易,也非常直观。现在,按照常规也许应该说明如何使用 DOM,或者至少要给出一些示例代码,但这样做也可能误导您。即使不理会 DOM,仍然能深入地探讨 Ajax,这也是我准备采用的方法。以后的文章将再次讨论 DOM,现在只要知道可能需要 DOM 就可以了。当需要在 JavaScript 代码和服务器之间传递 XML 和改变 HTML 表单的时候,我们再深入研究 DOM。没有它也能做一些有趣的工作,因此现在就把 DOM 放到一边吧。
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");。
这两行代码基本上就是尝试使用一个版本的 MSXML 创建对象,如果失败则使用另一个版本创建该对象。不错吧?如果都不成功,则将 xmlHttp 变量设为 false,告诉您的代码出现了问题。如果出现这种情况,可能是因为安装了非 Microsoft 浏览器,需要使用不同的代码。
处理 Mozilla 和非 Microsoft 浏览器
如果选择的浏览器不是 Internet Explorer,或者为非 Microsoft 浏览器编写代码,就需要使用不同的代码。事实上就是 清单 1 所示的一行简单代码:
var xmlHttp = new XMLHttpRequest object;。
这行简单得多的代码在 Mozilla、Firefox、Safari、Opera 以及基本上所有以任何形式或方式支持 Ajax 的非 Microsoft 浏览器中,创建了 XMLHttpRequest 对象。
结合起来
关键是要支持所有 浏览器。谁愿意编写一个只能用于 Internet Explorer 或者非 Microsoft 浏览器的应用程序呢?或者更糟,要编写一个应用程序两次?当然不!因此代码要同时支持 Internet Explorer 和非 Microsoft 浏览器。清单 4 显示了这样的代码。
清单 4. 以支持多种浏览器的方式创建 XMLHttpRequest 对象
/* Create a new XMLHttpRequest object to talk to the Web server */
var xmlHttp = false;
/*@cc_on @*/
/*@if (@_jscript_version >= 5)
try {
xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
} catch (e) {
try {
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
} catch (e2) {
xmlHttp = false;
}
}
@end @*/
您已经有了一个崭新的 XMLHttpRequest 对象,现在让它干点活儿吧。首先需要一个 Web 页面能够调用的 JavaScript 方法(比如当用户输入文本或者从菜单中选择一项时)。接下来就是在所有 Ajax 应用程序中基本都雷同的流程:
1、从 Web 表单中获取需要的数据。
2、建立要连接的 URL。
3、打开到服务器的连接。
4、设置服务器在完成后要运行的函数。
5、发送请求。
清单 5 中的示例 Ajax 方法就是按照这个顺序组织的:
清单 5. 发出 Ajax 请求
function callServer() {
// Get the city and state from the web form
var city = document.getElementById("city").value;
var state = document.getElementById("state").value;
// Only go on if there are values for both fields
if ((city == null) || (city == "")) return;
if ((state == null) || (state == "")) return;
// Build the URL to connect to
var url = "/scripts/getZipCode.php?city=" + escape(city) + "&state=" + escape(state);
// Open a connection to the server
xmlHttp.open("GET", url, true);
// Setup a function for the server to run when it's done
xmlHttp.onreadystatechange = updatePage;
// Send the request
xmlHttp.send(null);
}
其中大部分代码意义都很明确。开始的代码使用基本 JavaScript 代码获取几个表单字段的值。然后设置一个 PHP 脚本作为链接的目标。要注意脚本 URL 的指定方式,city 和 state(来自表单)使用简单的 GET 参数附加在 URL 之后。
function updatePage() {
if (xmlHttp.readyState == 4) {
var response = xmlHttp.responseText;
document.getElementById("zipCode").value = response;
}
}
这些代码同样既不难也不复杂。它等待服务器调用,如果是就绪状态,则使用服务器返回的值(这里是用户输入的城市和州的 ZIP 编码)设置另一个表单字段的值。于是包含 ZIP 编码的 zipCode 字段突然出现了,而用户没有按任何按钮!这就是前面所说的桌面应用程序的感觉。快速响应、动态感受等等,这些都只因为有了小小的一段 Ajax 代码。
细心的读者可能注意到 zipCode 是一个普通的文本字段。一旦服务器返回 ZIP 编码,updatePage() 方法就用城市/州的 ZIP 编码设置那个字段的值,用户就可以改写该值。这样做有两个原因:保持例子简单,说明有时候可能希望 用户能够修改服务器返回的数据。要记住这两点,它们对于好的用户界面设计来说很重要。
在深入研究代码之前首先看看最近的观点 ―― 一定要十分清楚 Web 2.0 这个概念。听到 Web 2.0 这个词的时候,应该首先问一问 “Web 1.0 是什么?” 虽然很少听人提到 Web 1.0,实际上它指的就是具有完全不同的请求和响应模型的传统 Web。比如,到 Amazon.com 网站上点击一个按钮或者输入搜索项。就会对服务器发送一个请求,然后响应再返回到浏览器。该请求不仅仅是图书和书目列表,而是另一个完整的 HTML 页面。因此当 Web 浏览器用新的 HTML 页面重绘时,可能会看到闪烁或抖动。事实上,通过看到的每个新页面可以清晰地看到请求和响应。
Web 2.0(在很大程度上)消除了这种看得见的往复交互。比如访问 Google Maps 或 Flickr 这样的站点(到这些支持 Web 2.0 和 Ajax 站点的链接请参阅 参考资料)。比如在 Google Maps 上,您可以拖动地图,放大和缩小,只有很少的重绘操作。当然这里仍然有请求和响应,只不过都藏到了幕后。作为用户,体验更加舒适,感觉很像桌面应用程序。这种新的感受和范型就是当有人提到 Web 2.0 时您所体会到的。
需要关心的是如何使这些新的交互成为可能。显然,仍然需要发出请求和接收响应,但正是针对每次请求/响应交互的 HTML 重绘造成了缓慢、笨拙的 Web 交互的感受。因此很清楚,我们需要一种方法使发送的请求和接收的响应只 包含需要的数据而不是整个 HTML 页面。惟一需要获得整个新 HTML 页面的时候就是希望用户看到 新页面的时候。
但多数交互都是在已有页面上增加细节、修改主体文本或者覆盖原有数据。这些情况下,Ajax 和 Web 2.0 方法允许在不 更新整个 HTML 页面的情况下发送和接收数据。对于那些经常上网的人,这种能力可以让您的应用程序感觉更快、响应更及时,让他们不时地光顾您的网站。
XMLHttpRequest 简介
要真正实现这种绚丽的奇迹,必须非常熟悉一个 JavaScript 对象,即 XMLHttpRequest。这个小小的对象实际上已经在几种浏览器中存在一段时间了,它是本专栏今后几个月中要介绍的 Web 2.0、Ajax 和大部分其他内容的核心。为了让您快速地大体了解它,下面给出将要用于该对象的很少的几个 方法和属性。
?open():建立到服务器的新请求。
?send():向服务器发送请求。
?abort():退出当前请求。
?readyState:提供当前 HTML 的就绪状态。
?responseText:服务器返回的请求响应文本。
代码非常简单,对大多数 JavaScript 和 Web 开发人员来说,真正理解它要比读写代码花更长的时间。现在已经得到了一段带有错误检查的 XMLHttpRequest 对象创建代码,还可以告诉您哪儿出了问题。
应付 Microsoft
看起来似乎一切良好,至少在用 Internet Explorer 试验这些代码之前是这样的。如果这样试验的话,就会看到 图 1 所示的糟糕情形。
图 1. Internet Explorer 报告错误
显然有什么地方不对劲,而 Internet Explorer 很难说是一种过时的浏览器,因为全世界有 70% 在使用 Internet Explorer。换句话说,如果不支持 Microsoft 和 Internet Explorer 就不会受到 Web 世界的欢迎!因此我们需要采用不同的方法处理 Microsoft 浏览器。
经验证发现 Microsoft 支持 Ajax,但是其 XMLHttpRequest 版本有不同的称呼。事实上,它将其称为几种 不同的东西。如果使用较新版本的 Internet Explorer,则需要使用对象 Msxml2.XMLHTTP,而较老版本的 Internet Explorer 则使用 Microsoft.XMLHTTP。我们需要支持这两种对象类型(同时还要支持非 Microsoft 浏览器)。请看看 清单 4,它在前述代码的基础上增加了对 Microsoft 的支持。
Microsoft 参与了吗?
关于 Ajax 和 Microsoft 对该领域不断增长的兴趣和参与已经有很多文章进行了介绍。事实上,据说 Microsoft 最新版本的 Internet Explorer ―― version 7.0,将在 2006 年下半年推出 ―― 将开始直接支持 XMLHttpRequest,让您使用 new 关键字代替所有的 Msxml2.XMLHTTP 创建代码。但不要太激动,仍然需要支持旧的浏览器,因此跨浏览器代码不会很快消失。作者: tznktg 时间: 2007-9-19 23:17
清单 4. 增加对 Microsoft 浏览器的支持
function getCustomerInfo() {
var phone = document.getElementById("phone").value;
var url = "/cgi-local/lookupCustomer.php?phone=" + escape(phone);
request.open("GET", url, true);
}
一旦设置好了 URL,其他就简单了。多数请求使用 GET 就够了(后面的文章中将看到需要使用 POST 的情况),再加上 URL,这就是使用 open() 方法需要的全部内容了。
挑战异步性
本系列的后面一篇文章中,我将用很多时间编写和使用异步代码,但是您应该明白为什么 open() 的最后一个参数这么重要。在一般的请求/响应模型中,比如 Web 1.0,客户机(浏览器或者本地机器上运行的代码)向服务器发出请求。该请求是同步的,换句话说,客户机等待服务器的响应。当客户机等待的时候,至少会用某种形式通知您在等待:
?沙漏(特别是 Windows 上)。
?旋转的皮球(通常在 Mac 机器上)。
?应用程序基本上冻结了,然后过一段时间光标变化了。
这正是 Web 应用程序让人感到笨拙或缓慢的原因 ―― 缺乏真正的交互性。按下按钮时,应用程序实际上变得不能使用,直到刚刚触发的请求得到响应。如果请求需要大量服务器处理,那么等待的时间可能很长(至少在这个多处理器、DSL 没有等待的世界中是如此)。
而异步请求不 等待服务器响应。发送请求后应用程序继续运行。用户仍然可以在 Web 表单中输入数据,甚至离开表单。没有旋转的皮球或者沙漏,应用程序也没有明显的冻结。服务器悄悄地响应请求,完成后告诉原来的请求者工作已经结束(具体的办法很快就会看到)。结果是,应用程序感觉不 那么迟钝或者缓慢,而是响应迅速、交互性强,感觉快多了。这仅仅是 Web 2.0 的一部分,但它是很重要的一部分。所有老套的 GUI 组件和 Web 设计范型都不能克服缓慢、同步的请求/响应模型。
function updatePage() {
if (request.readyState == 4)
alert("Server is done!");
}
修改后就可以保证服务器的处理已经完成。尝试运行新版本的 Ajax 代码,现在就会看到与预期的一样,只显示一次警告信息了。
HTTP 状态码
虽然 清单 13 中的代码看起来似乎不错,但是还有一个问题 ―― 如果服务器响应请求并完成了处理但是报告了一个错误怎么办?要知道,服务器端代码应该明白它是由 Ajax、JSP、普通 HTML 表单或其他类型的代码调用的,但只能使用传统的 Web 专用方法报告信息。而在 Web 世界中,HTTP 代码可以处理请求中可能发生的各种问题。
function updatePage() {
if (request.readyState == 4)
if (request.status == 200)
alert("Server is done!");
}
为了增加更健壮的错误处理并尽量避免过于复杂,可以增加一两个状态码检查,请看一看 清单 15 中修改后的 updatePage() 版本。
清单 15. 增加一点错误检查
function updatePage() {
if (request.readyState == 4)
if (request.status == 200)
alert("Server is done!");
else if (request.status == 404)
alert("Request URL does not exist");
else
alert("Error: status code is " + request.status);
}
现在将 getCustomerInfo() 中的 URL 改为不存在的 URL 看看会发生什么。应该会看到警告信息说明要求的 URL 不存在 ―― 好极了!很难处理所有的错误条件,但是这一小小的改变能够涵盖典型 Web 应用程序中 80% 的问题。
Microsoft™ 和 Internet Explorer 使用了一个名为 XMLHttp 的对象,而不是 XMLHttpRequest 对象,而 Mozilla、Opera、Safari 和 大部分非 Microsoft 浏览器都使用的是后者。为了简单性起见,我将这两个对象都简单地称为 XMLHttpRequest。这既符合我们在 Web 上看到的情况,又符合 Microsoft 在 Internet Explorer 7.0 中使用 XMLHttpRequest 作为请求对象的意图。(有关这个问题的更多内容,请参见 第 2 部分。)
清单 1. 在回调函数中处理服务器的响应
function updatePage() {
if (request.readyState == 4) {
if (request.status == 200) {
var response = request.responseText.split("|");
document.getElementById("order").value = response[0];
document.getElementById("address").innerHTML =
response[1].replace(/\n/g, "<br />");
} else
alert("status is " + request.status);
}
}
这显然是就绪状态最常见(也是最简单)的用法。正如您从数字 "4" 中可以看出的一样,还有其他几个就绪状态(您在上一篇文章中也看到过这个清单 ―― 请参见 参考资料):
function updatePage() {
// Output the current ready state
alert("updatePage() called with ready state of " + request.readyState);
}
如果您不确定如何运行这个函数,就需要创建一个函数,然后在 Web 页面中调用这个函数,并让它向服务器端的组件发送一个请求(例如 清单 2 给出的函数,或本系列文章的第 1 部分和第 2 部分中给出的例子)。确保在建立请求时,将回调函数设置为 updatePage();要实现这种设置,可以将请求对象的 onreadystatechange 属性设置为 updatePage()。
function updatePage() {
if (request.readyState == 4) {
var newTotal = request.responseText;
var totalSoldEl = document.getElementById("total-sold");
var netProfitEl = document.getElementById("net-profit");
replaceText(totalSoldEl, newTotal);
/* 图 out the new net profit */
var boardCostEl = document.getElementById("board-cost");
var boardCost = getText(boardCostEl);
var manCostEl = document.getElementById("man-cost");
var manCost = getText(manCostEl);
var profitPerBoard = boardCost - manCost;
var netProfit = profitPerBoard * newTotal;
/* Update the net profit on the sales form */
netProfit = Math.round(netProfit * 100) / 100;
replaceText(netProfitEl, netProfit);
}
清单 1 相当简单;清单 4 稍微有点复杂,但是它们在开始时都要检查就绪状态,并获取 responseText 属性的值。
function updatePage() {
// Output the current ready state
alert("updatePage() called with ready state of " + request.readyState +
" and a response text of '" + request.responseText + "'");
}
现在在浏览器中打开 Web 应用程序,并激活您的请求。要更好地看到这段代码的效果,请使用 Firefox 或 Internet Explorer,因为这两个浏览器都可以报告出请求过程中所有可能的就绪状态。例如在就绪状态 2 中,就没有定义 responseText (请参见 图 3);如果 JavaScript 控制台也已经打开了,您就会看到一个错误。
function updatePage() {
if (request.readyState == 4) {
if (request.status == 200) {
var response = request.responseText.split("|");
document.getElementById("order").value = response[0];
document.getElementById("address").innerHTML =
response[1].replace(/\n/g, "<br />");
} else if (request.status == 404) {
alert ("Requested URL is not found.");
} else if (request.status == 403) {
alert("Access denied.");
} else
alert("status is " + request.status);
}
}
虽然这依然相当简单,但是它的确多提供了一些有用的信息。图 6 给出了与 图 5 相同的错误,但是这一次错误处理代码向用户或程序员更好地说明了究竟发生了什么。
图 6. 特殊错误处理
在我们自己的应用程序中,可以考虑在发生认证失败的情况时清除用户名和密码,并向屏幕上添加一条错误消息。我们可以使用类似的方法来更好地处理找不到脚本或其他 400 类型的错误(例如 405 表示不允许使用诸如发送 HEAD 请求之类不可接受的请求方法,而 407 则表示需要进行代理认证)。然而不管采用哪种选择,都需要从对服务器上返回的状态代码开始入手进行处理。
其他请求类型
如果您真希望控制 XMLHttpRequest 对象,可以考虑最后实现这种功能 ―― 将 HEAD 请求添加到指令中。在前两篇文章中,我们已经介绍了如何生成 GET 请求;在马上就要发表的一篇文章中,您会学习有关使用 POST 请求将数据发送到服务器上的知识。不过本着增强错误处理和信息搜集的精神,您应该学习如何生成 HEAD 请求。
function getSalesData() {
createRequest();
var url = "/boards/servlet/UpdateBoardSales";
request.open("HEAD", url, true);
request.onreadystatechange = updatePage;
request.send(null);
}
当您这样生成一个 HEAD 请求时,服务器并不会像对 GET 或 POST 请求一样返回一个真正的响应。相反,服务器只会返回资源的 头(header),这包括响应中内容最后修改的时间、请求资源是否存在和很多其他有用信息。您可以在服务器处理并返回资源之前使用这些信息来了解有关资源的信息。
对于这种请求您可以做的最简单的事情就是简单地输出所有的响应头的内容。这可以让您了解通过 HEAD 请求可以使用什么。清单 10 提供了一个简单的回调函数,用来输出从 HEAD 请求中获得的响应头的内容。
清单 10. 输出从 HEAD 请求中获得的响应头的内容
function updatePage() {
if (request.readyState == 4) {
alert(request.getAllResponseHeaders());
}
}
请参见 图 7,其中显示了从一个向服务器发出的 HEAD 请求的简单 Ajax 应用程序返回的响应头。
您可以单独使用这些头(从服务器类型到内容类型)在 Ajax 应用程序中提供其他信息或功能。
检查 URL
您已经看到了当 URL 不存在时应该如何检查 404 错误。如果这变成一个常见的问题 ―― 可能是缺少了一个特定的脚本或 servlet ―― 那么您就可能会希望在生成完整的 GET 或 POST 请求之前来检查这个 URL。要实现这种功能,生成一个 HEAD 请求,然后在回调函数中检查 404 错误;清单 11 给出了一个简单的回调函数。
清单 11. 检查某个 URL 是否存在
function updatePage() {
if (request.readyState == 4) {
if (request.status == 200) {
alert("URL exists");
} else if (request.status == 404) {
alert("URL does not exist.");
} else {
alert("Status is: " + request.status);
}
}
}
诚实地说,这段代码的价值并不太大。服务器必须对请求进行响应,并构造一个响应来填充内容长度的响应头,因此并不能节省任何处理时间。另外,这花费的时间与生成请求并使用 HEAD 请求来查看 URL 是否存在所需要的时间一样多,因为它要生成使用 GET 或 POST 的请求,而不仅仅是如 清单 7 所示一样来处理错误代码。不过,有时确切地了解目前什么可用也是非常有用的;您永远不会知道何时创造力就会迸发或者何时需要 HEAD 请求!
有用的 HEAD 请求
您会发现 HEAD 请求非常有用的一个领域是用来查看内容的长度或内容的类型。这样可以确定是否需要发回大量数据来处理请求,和服务器是否试图返回二进制数据,而不是 HTML、文本或 XML(在 JavaScript 中,这 3 种类型的数据都比二进制数据更容易处理)。
在很多应用程序中,生成 HEAD 请求并没有增加任何功能,甚至可能会导致请求速度变慢(通过强制生成一个 HEAD 请求来获取有关响应的数据,然后在使用一个 GET 或 POST 请求来真正获取响应)。然而,在出现您不确定有关脚本或服务器端组件的情况时,使用 HEAD 请求可以获取一些基本的数据,而不需要对响应数据真正进行处理,也不需要大量的带宽来发送响应。
结束语
对于很多 Ajax 和 Web 程序员来说,本文中介绍的内容似乎是太高级了。生成 HEAD 请求的价值是什么呢?到底在什么情况下需要在 JavaScript 中显式地处理重定向状态代码呢?这些都是很好的问题;对于简单的应用程序来说,答案是这些高级技术的价值并不是非常大。
?如果您可以考虑各种就绪状态 ―― 并且理解了这些就绪状态在不同浏览器之间的区别 ―― 就可以快速调试应用程序了。您甚至可以基于就绪状态而开发一些创造性的功能,并向用户和客户回报请求的状态。
?如果您要对状态代码进行控制,就可以设置应用程序来处理脚本错误、非预期的响应以及边缘情况。结果是应用程序在所有的时间都可以正常工作,而不仅仅是只能一切都正常的情况下才能运行。
?增加这种生成 HEAD 请求的能力,检查某个 URL 是否存在,以及确认某个文件是否被修改过,这样就可以确保用户可以获得有效的页面,用户所看到的信息都是最新的,(最重要的是)让他们惊讶这个应用程序是如何健壮和通用。
本文的目的并非是要让您的应用程序显得十分华丽,而是帮助您去掉黄色聚光灯后重点昭显文字的美丽,或者外观更像桌面一样。尽管这些都是 Ajax 的功能(在后续几篇文章中就会介绍),不过它们却像是蛋糕表面的一层奶油。如果您可以使用 Ajax 来构建一个坚实的基础,让应用程序可以很好地处理错误和问题,用户就会返回您的站点和应用程序。在接下来的文章中,我们将添加这种直观的技巧,这会让客户兴奋得发抖。(认真地说,您一定不希望错过下一篇文章!)作者: tznktg 时间: 2007-9-19 23:19
第 4 页 利用 DOM 进行 Web 响应
程序员(使用后端应用程序)和 Web 程序员(编写 HTML、CSS 和 JavaScript)之间的分水岭是长久存在的。但是,Document Object Model (DOM) 弥补了这个裂缝,使得在后端使用 XML 同时在前端使用 HTML 切实可行,并成为极其有效的工具。在本文中,Brett McLaughlin 介绍了 Document Object Model,解释它在 Web 页面中的应用,并开始挖掘其在 JavaScript 中的用途。
与许多 Web 程序员一样,您可能使用过 HTML。HTML 是程序员开始与 Web 页面打交道的方式;HTML 通常是他们完成应用程序或站点前的最后一步――调整一些布局、颜色或样式。不过,虽然经常使用 HTML,但对于 HTML 转到浏览器呈现在屏幕上时到底发生了什么,人们普遍存在误解。在我分析您认为可能发生的事情及其可能错误的原因之前,我希望您对设计和服务 Web 页面时涉及的过程一清二楚:
1、一些人(通常是您!)在文本编辑器或 IDE 中创建 HTML。
2、然后您将 HTML 上载到 Web 服务器,例如 Apache HTTPD,并将其公开在 Internet 或 intranet 上。
3、用户用 Firefox 或 SafariA 等浏览器请求您的 Web 页面。
4、用户的浏览器向您的服务器请求 HTML。
5、浏览器将从服务器接收到的页面以图形和文本方式呈现;用户看到并激活 Web 页面。
对于多数程序员,当 Web 浏览器开始时他们的工作就结束了。也就是说,将一个 HTML 文件放入 Web 浏览器的目录上后,您通常就认为它已经“完成”,而且(满怀希望地)认为再也不会考虑它!说到编写干净、组织良好的页面时,这也是一个伟大的目标;希望您的标记跨浏览器、用各种版本的 CSS 和 JavaScript 显示它应该显示的内容,一点错都没有。
问题是这种方法限制了程序员对浏览器中真正发生的事情的理解。更重要的是,它限制了您用客户端 JavaScript 动态更新、更改和重构 Web 页面的能力。摆脱这种限制,让您的 Web 站点拥有更大的交互性和创造性。
程序员做什么
作为典型的 Web 程序员,您可能启动文本编辑和 IDE 后就开始输入 HTML、CSS 甚至 JavaScript。很容易认为这些标记、选择器和属性只是使站点正确显示而做的小小的任务。但是,在这一点上您需要拓展您的思路,要意识到您是在组织您的内容。不要担心;我保证这不会变成关于标记美观、您必须如何认识到 Web 页面的真正潜力或其他任何元物质的讲座。您需要了解的是您在 Web 开发中到底是什么角色。
说到页面的外观,顶多您只能提提建议。您提供 CSS 样式表时,用户可以覆盖您的样式选择。您提供字体大小时,用户浏览器可以为视障者更改这些大小,或者在大显示器(具有同等大的分辨率)上按比例缩小。甚至您选择的颜色和字体也受制于用户显示器和用户在其系统上安装的字体。虽然尽您所能来设计页面样式很不错,但这绝不是 您对 Web 页面的最大影响。
您绝对控制的是 Web 页面的结构。您的标记不可更改,用户就不能乱弄;他们的浏览器只能从您的 Web 服务器检索标记并显示它(虽然样式更符合用户的品味而不是您自己的品味)。但页面组织,不管是在该段落内还是在其他分区,都只由您单独决定。要是想实际更改您的页面(这是大多数 Ajax 应用程序所关注的),您操作的是页面的结构。尽管很容易更改一段文本的颜色,但在现有页面上添加文本或整个区段要难得多。不管用户如何设计该区段的样式,都是由您控制页面本身的组织。
在讨论 Web 浏览器之前,值得考虑一下为什么纯文本绝对 是存储 HTML 的最佳选择(有关详细信息,请参阅 有关标记的一些其他想法)。不考虑优缺点,只是回忆一下在每次查看页面时 HTML 是通过网络发送到 Web 浏览器的(为了简洁,不考虑高速缓存等)。真是再没有比传递文本再有效的方法了。二进制对象、页面图形表示、重新组织的标记块等等,所有这一切都比纯文本文件通过网络传递要更困难。
现在,花点时间展开一些 HTML 文档并用树将其勾画出来。尽管这看起来是个不寻常的请求(尤其是在包含极少代码的这样一篇文章中),如果您希望能够操纵这些树,那么需要熟悉它们的结构。
在这个过程中,可能会发现一些古怪的事情。比如,考虑下列情况:
?属性发生了什么?
?分解为元素(比如 em 和 b)的文本呢?
?结构不正确(比如当缺少结束 p 标记时)的 HTML 呢?
一旦熟悉这些问题之后,就能更好地理解下面几节了。
严格有时是好事
如果尝试刚提到的练习 I,您可能会发现标记的树视图中存在一些潜在问题(如果不练习的话,那就听我说吧!)。事实上,在 清单 1 和 图 1 中就会发现一些问题,首先看 p 元素是如何分解的。如果您问通常的 Web 开发人员 “p 元素的文本内容是什么”,最常见的答案将是 “Welcome to a really boring Web page.”。如果将之与图 1 做比较,将会发现这个答案(虽然合乎逻辑)是根本不正确的。
实际上,p 元素具有三个 不同的子对象,其中没有一个包含完整的 “Welcome to a really boring Web page.” 文本。您会发现文本的一部分,比如 “Welcome to a ” 和 “ boring Web page”,但不是全部。为了理解这一点,记住标记中的任何内容都必须转换为某种类型的对象。
此外,顺序无关紧要!如果浏览器显示正确的对象,但显示顺序与您在 HTML 中提供的顺序不同,那么您能想像出用户将如何响应 Web 浏览器吗?段落夹在页面标题和文章标题中间,而这不是您自己组织文档时的样式呢?很显然,浏览器必须保持元素和文本的顺序。
在本例中,p 元素有三个不同部分:
?em 元素之前的文本
?em 元素本身
?em 元素之后的文本
如果将该顺序打乱,可能会把重点放在文本的错误部分。为了保持一切正常,p 元素有三个子对象,其顺序是在 清单 1 的 HTML 中显示的顺序。而且,重点文本 “really” 不是p 的子元素;而是 p 的子元素 em 的子元素。
理解这一概念非常重要。尽管 “really” 文本将可能与其他 p 元素文本一起显示,但它仍是 em 元素的直接子元素。它可以具有与其他 p 文本不同的格式,而且可以独立于其他文本到处移动。
<html>
<head>
<title>This is a little tricky</title>
</head>
<body>
<h1>Pay <u>close</u> attention, OK?</h1>
<div>
<p>This p really isn't <em>necessary</em>, but it makes the
<span id="bold-text">structure <i>and</i> the organization</span>
of the page easier to keep up with.</p>
</div>
</body>
</html>作者: tznktg 时间: 2007-9-19 23:20
清单 3. 更巧妙的元素嵌套
<html>
<head>
<title>Trickier nesting, still</title>
</head>
<body>
<div id="main-body">
<div id="contents">
<table>
<tr><th>Steps</th><th>Process</th></tr>
<tr><td>1</td><td>Figure out the <em>root element</em>.</td></tr>
<tr><td>2</td><td>Deal with the <span id="code">head</span> first,
as it's usually easy.</td></tr>
<tr><td>3</td><td>Work through the <span id="code">body</span>.
Just <em>take your time</em>.</td></tr>
</table>
</div>
<div id="closing">
This link is <em>not</em> active, but if it were, the answers
to this <a href="answers.html"><img src="exercise.gif" /></a> would
be there. But <em>do the exercise anyway!</em>
</div>
</div>
</body>
</html>
在本文末的 GIF 文件 图 2 中的 tricky-solution.gif 和 图 3 中的 trickier-solution.gif 中将会找到这些练习的答案。不要偷看,先花些时间自动解答一下。这样能帮助您理解组织树时应用的规则有多么严格,并真正帮助您掌握 HTML 及其树结构。
到目前为止,您已经知道浏览器将 Web 页面转换为对象表示,可能您甚至会猜想,对象表示是 DOM 树。DOM 表示 Document Object Model,是一个规范,可从 World Wide Web Consortium (W3C) 获得(您可以参阅 参考资料 中的一些 DOM 相关链接)。
但更重要的是,DOM 定义了对象的类型和属性,从而允许浏览器表示标记。(本系列下一篇文章将专门讲述在 JavaScript 和 Ajax 代码中使用 DOM 的规范。)
文档对象
首先,需要访问对象模型本身。这非常容易;要在运行于 Web 页面上的任何 JavaScript 代码中使用内置 document 变量,可以编写如下代码:
var domTree = document;
当然,该代码本身没什么用,但它演示了每个 Web 浏览器使得 document 对象可用于 JavaScript 代码,并演示了对象表示标记的完整树(图 1)。
每项都是一个节点
显然,document 对象很重要,但这只是开始。在进一步深入之前,需要学习另一个术语:节点。您已经知道标记的每个部分都由一个对象表示,但它不只是一个任意的对象,它是特定类型的对象,一个 DOM 节点。更特定的类型,比如文本、元素和属性,都继承自这个基本的节点类型。所以可以有文本节点、元素节点和属性节点。
如果已经有很多 JavaScript 编程经验,那您可能已经在使用 DOM 代码了。如果到目前为止您一直在跟踪本 Ajax 系列,那么现在您一定 使用 DOM 代码有一段时间了。例如,代码行 var number = document.getElementById("phone").value; 使用 DOM 查找特定元素,然后检索该元素的值(在本例中是一个表单字段)。所以即使您没有意识到这一点,但您每次将 document 键入 JavaScript 代码时都会使用 DOM。
详细解释已经学过的术语,DOM 树是对象的树,但更具体地说,它是节点 对象的树。在 Ajax 应用程序中或任何其他 JavaScript 中,可以使用这些节点产生下列效果,比如移除元素及其内容,突出显示特定文本,或添加新图像元素。因为都发生在客户端(运行在 Web 浏览器中的代码),所以这些效果立即发生,而不与服务器通信。最终结果通常是应用程序感觉起来响应更快,因为当请求转向服务器时以及解释响应时,Web 页面上的内容更改不会出现长时间的停顿。
var domTree = document;
var phoneNumberElement = document.getElementById("phone");
var phoneNumber = phoneNumberElement.value;
没有类型,JavaScript 根据需要创建变量并为其分配正确的类型。结果,从 JavaScript 中使用 DOM 变得微不足道(将来有一篇文章会专门讲述与 XML 相关的 DOM,那时将更加巧妙)。
结束语
在这里,我要给您留一点悬念。显然,这并非是对 DOM 完全详尽的说明;事实上,本文不过是 DOM 的简介。DOM 的内容要远远多于我今天介绍的这些!
本系列的下一篇文章将扩展这些观点,并深入探讨如何在 JavaScript 中使用 DOM 来更新 Web 页面、快速更改 HTML 并为您的用户创建更交互的体验。在后面专门讲述在 Ajax 请求中使用 XML 的文章中,我将再次返回来讨论 DOM。所以要熟悉 DOM,它是 Ajax 应用程序的一个主要部分。
此时,深入了解 DOM 将十分简单,比如详细设计如何在 DOM 树中移动、获得元素和文本的值、遍历节点列表,等等,但这可能会让您有这种印象,即 DOM 是关于代码的,而事实上并非如此。
在阅读下一篇文章之前,试着思考一下树结构并用一些您自己的 HTML 实践一下,以查看 Web 浏览器是如何将 HTML 转换为标记的树视图的。此外,思考一下 DOM 树的组织,并用本文介绍的特殊情况实践一下:属性、有元素混合在其中的文本、没有 文本内容的元素(比如 img 元素)。
如果扎实掌握了这些概念,然后学习了 JavaScript 和 DOM 的语法(下一篇文章),则会使得响应更为容易。