简介
编写 JavaScript 和 Asynchronous JavaScript + XML (Ajax) 应用程序时,非常容易专注于它们必须提供的交互式特性,而忘记 web 应用程序开发的基本原则。以一种低调的方式来编写 JavaScript 和 Ajax 应用程序很重要,原因有几点。第一,这样做允许应用程序逻辑与您的内容分离,便于您轻松维护应用程序。另外,它让您确保您的应用程序以其最基本的形式在各个平台和 web 浏览器之间一致地运行,让您在添加 JavaScript 和 Ajax 特性时只需关注这个问题。最重要的也许是,以这种方式开发 web 应用程序意味着遵守渐进式增强(progressive enhancement) 理念,即您的应用程序将支持使用不支持 JavaScript、尤其是 JavaScript 特性(包括 Ajax)的浏览器的用户。如果到目前为止您一直以一种高调的方式编写 JavaScript 应用程序,本文将帮助您发现一些允许您创建适合所有人的 web 应用程序的最佳实践,同时向使用它们的用户提供所有诱人的附加功能。
术语低调的(unobtrusive)JavaScript 的定义相对宽泛,但被公认为是使用一个优秀编程实践集合来创建 web 页面和应用程序的过程。这些编程实践包括:
- 使您的应用程序的 JavaScript、CSS 和 HTML 元素保持分离。
- 使用 JavaScript 逐渐增强您的应用程序 — 不要使用 JavaScript 实现核心功能
- 以能够减少重复、更具组织性、更易于读取和维护的方式维护您的代码结构
- 遵守 web 和可访问性标准
以这种方式进行开发不仅是一种良好实践,而且能确保您的应用程序将适用使用不同 web 浏览器和设备的广泛用户,即使是功能有限的浏览器或设备。以这种方式构建的应用程序通常拥有更好的组织形式和结构,执行速度更快,更不容易出现 bug。
在本文中,您将看到您的应用程序的呈现层、样式层和行为层应该如何保持分离,以便不进行内联 CSS 或 JavaScript 事件处理。您还将看到一些高调的 JavaScript 代码示例,并发现它们拥有的、被认为是不好的编程实践的属性。然后,您将了解如何更正这些问题,以一种低调的方式编写相同的代码,并了解这种开发风格的一些最佳实践的原则。 特别是,Ajax 应用程序是低调的代码的危险基础。只是因为您的应用程序拥有一个富 Ajax 接口并不意味着不能以一种渐进式增强的方式添加这个代码。 您将了解如何以一种特别的方式接近 Ajax 函数,这种方式将向不能利用您的 Ajax 特性拥有的流动性(fluidity)的用户提供一个回退操作(fallback)。最后,您将看到一个详细的应用程序示例,它提供即便在 JavaScript 关闭时仍能工作的动态 Ajax 加载。
回页首
分离行为和内容
在 Ajax 和 Web 2.0 出现之前,JavaScript 广泛用于一些基本特性,比如客户端表单验证、滚动图像、以及显示/隐藏内容。由于这些特性中的每一个都通过很少的 JavaScript 代码支持,一般趋势是在您的 HTML 文档的 区域中的一个
块中创建两个函数,然后使用
onclick
这样的 HTML 元素上的属性将那些函数附加到一些事件,例如: 。
JavaScript 并不是分离您的代码的不同部分时遇到的惟一问题。CSS 样式化通常也包含在行内。检查下面的示例: 。
以这种编码风格更改一个元素的外观和行为通常一开始没有什么坏处。问题是,以这种方法创建的代码越多,代码将越来越难以管理,要以一种低调的方式进行开发将变得越来越困难。在大型应用程序中更改以内联方式编写的 JavaScript 和 CSS 代码简直是一场噩梦,因为需要在很多很多地方进行更改 — 在此过程中非常容易错过某些内容。
取代以内联方式编码的是,应该将所有 JavaScript 和 CSS 代码保持在您的 HTML 标记之外。应使用 id
和 class
等引用属性,以便您的 JavaScript 和 CSS 更容易在 DOM 中找到并识别这些元素。如果遇到这个按钮,可以按清单 1 所示的方式重写。本文在所有示例中都使用 Prototype JavaScript 库;如果愿意,也可以使用喜欢的库或原始 JavaScript 来替代这个库。参见 参考资料 获取一篇比较几个 JavaSript 框架的文章的链接。
清单 1. 将您的 JavaScript 和 CSS 代码保持在 HTML 标记之外
// HTML code // JavaScript code $("my_button").observe("click", buttonPressed); // CSS code #my_button { background-color: #999; } |
正如不要在您的 HTML 标记中混合 CSS 和 JavaScript 代码很重要一样,不要将 CSS 代码混入您的 JavaScript 也很重要。例如,清单 1 中的 buttonPressed
函数可能会导致一个元素的样式发生改变。请看清单 2 中的示例。
清单 2. 这个示例展示
buttonPressed
函数如何意外更改一个元素的样式
function buttonPressed() { $("my_div").setStyle({ backgroundColor: "#FF6600", fontSize: "12px" }) } |
如您所见,这个代码直接操作样式,这不太好,因为这种工作应该留给 CSS 完成。相反,您应该使用 CSS 来定义一个给定类名的样式属性,然后使用 JavaScript 将那个类应用到相关对象。因此,清单 2 中的代码可以进行改进,如清单 3 所示。
清单 3. 改进代码
// JavaScript code function buttonPressed() { var my_div = $("my_div"); if(!my_div.hasClassName("highlight")) my_div.addClassName("highlight"); } // CSS code: .highlight { background-color: #FF6600; font-size: 12px; } |
以这种方式分离您的代码将产生一个组织良好的代码结构,使得诊断和修复可能出现的问题要容易得多。还建议您将您的 JavaScript 和 CSS 代码放置到外部文件中,而不是将它们包含在您的 HTML 代码中(即便它们只是驻留在 区域中)。在大多数案例中,这还将有助于加速您的应用程序,因为外部文件在第一次被访问时缓存,不需要在下一个页面上再次下载。
尽管在技术上可以构建采用渐进式增强同时仍然使用内联代码的 JavaScript 应用程序,然而这样做将导致难以维护的混乱代码。只要可能,就应该总是使您的 JavaScript、CSS 和 HTML 分离,这样对您有好处。
回页首
高调的 JavaScript 的应用
描述高调的 JavaScript 的最好方法是提供一个示例。我们经常会看到用以下方式创建的链接: Click me
。
您可能会发现一个用 JavaScript 编码的事件处理程序,如下所示: $("my_link").observe("click", validateAndSubmit);
。
validateAndSubmit
函数可能会包含一些验证规则,如果通过验证,可能会提交一个表单,或者执行其他动作。尽管这看起来可能完全没有坏处,但如果不启用 JavaScript,这个链接根本不会工作。浏览器将会寻找 URL #,当然,这个 URL 是您当前所在的页面的锚点,然后停止。
注意:一个规则是,不应该依赖客户端验证,因为 JavaScript 很容易被关闭或绕过,使其成为一种非常不安全和不可靠的表单和输入验证方法。相反,客户端验证只应该用作改善用户体验并减少发送到服务器的无效表单数量的方法。您应该总是在服务器端验证并清洁输入。
在这种情况下,客户端验证仅当 JavaScript 启用时才能发挥作用。修复这个链接可能与创建下面的链接一样简单: Click me
。
现在,在您的 JavaScript 代码中,您可以决定是否应该遵循这个链接(见清单 4)。
清单 4. 决定是否应该遵循这个链接
function validateAndSubmit() { //validation logic here if(valid) { return true; } else { alert("Error!"); return false; } } |
在清单 4 中,如果 valid
返回 false,将打开一个警告框,链接不会被遵循,因为事件处理程序返回一个 false 值。
低调的 JavaScript 的另一个常见用途是在快速跳转(quick jump) 下拉列表中。清单 4 展示了一个示例。
清单 5. 快速跳转下拉列表
// HTML code // JavaScript code function goToSearch(e) { var el = Event.element(e); if($F(el).length > 0) window.location = $F(el); } document.observe("dom:loaded", function() { $("my_select").observe("change", goToSearch); }); |
同样,在这个示例中,如果 JavaScript 被禁用,当您从下拉列表中选择一个项目时,什么也不会发生。这里的问题是,没有一个标准 HTML 回退操作来导航到一个可用页面。我们来重写这个示例,这次使用渐进式增强来包含 JavaScript 函数(见清单 6)。
清单 6. 渐进式增强
// HTML code: |
在这个示例中,我们使用了一个 HTML 元素,该元素允许您定义一个服务器端脚本,用于在 JavaScript 不可用时负责重定向。 如果 JavaScript 被关闭,当用户单击 Go! 按钮时,表单将提交到 redirect.php,在查询字符串中提交 URL。然后服务器可以验证是否选中有效选项,并根据接收到的输入重定向输出。
在这个示例中,您可以使用 JavaScript 按照以下两种方法渐进地增强这一点:
- 验证用户是否已经选择了一个搜索引擎
- 执行重定向,但不需要发送另一个 HTTP 请求到 web 服务器
我们继续进行,使用 JavaScript 来实现这些改进(见清单 7)。
清单 7. 使用 JavaScript 实现改进
// JavaScript code function goToSearch(e) { Event.stop(e); var my_select = $F("my_select"); if(my_select.length > 0) window.location = my_select; else alert("You must select a search engine!"); } document.observe("dom:loaded", function() { document.redirect.observe("submit", goToSearch); }); |
清单 7 停止默认动作执行(阻止表单提交到服务器),然后验证是否选择了搜索引擎。如果是这样,它将用户重定向到选中的 URL。否则,它显示一个包含错误消息的警告框。
您可能已经注意到,清单 6 中的示例添加了一个 Go! 按钮,而原始示例在 元素本身上的
onchange
事件上工作。这里的问题是您不能提交一个只包含这个元素而没有提交按钮或 JavaScript 的表单。如果您决定不包含 Go! 按钮,则可以使用 JavaScript 来动态设置按钮的样式,以便它在启用了 JavaScript 的浏览器上被隐藏。这样,没有启用 JavaScript 的浏览器中仍将显示按钮,而启用了 JavaScript 的浏览器中将隐藏按钮。然后,您可以将事件处理程序附加到 元素的
onchange
事件上,从而拥有两种情况下的最佳功能。
回页首
编写低调的代码
为了以一种低调的方式编写代码,您应该首先开发一个适用最低公共特性 — 不支持 JavaScript 的浏览器 — 的解决方案。大多数现代浏览器要么拥有一个内置选项来禁用 JavaScript,要么拥有一个可用插件。通过没有 JavaScript 可用的用户的视角来查看您的站点。
从编写没有 JavaScript 的 HTML 代码开始,然后编写 HTML,以便它提供页面需要提供的所有基本功能。如果您需要提交输入数据,使用一个带真实动作属性的 当您对没有任何 JavaScript 的应用程序和 web 页面的功能比较满意,而且它们能够满足预定的基本要求,您可以继续使用 JavaScript 以渐进式方式增强页面。通过这种方式开发您的应用程序,您的页面将拥有更多语义含义,因为它更可能使用 HTML 元素来实现它们预定的目标。这种开发方式的其他好处之一是以其他方式开发可能会丧失的特性仍然能够受到支持。 这种方式的一个不错的示例是使用没有 下面我们来逐步检查以低调的方式编写一个代码的简单示例。假设您有一个缩略图(thumbnail image),您想允许用户单击这个缩略图并在一个 lightbox(出现的一种模式弹出窗口,使用一种屏蔽叠加效果使页面淡出到背景中)中显示该缩略图的大图版本。不要只是创建一个 需要做的第一件事就是弄清您正在努力完成的任务。您想单击一个图像,显示该图像的一个大图,但不离开当前所在的页面。因此,您想添加美观的 lightbox 效果,但要满足基本要求;这就是您需要完成的主要任务。只使用 HTML 的解决方案非常简单(见清单 8)。 清单 8 包含只使用简单 HTML 实现的基本要求。这不错,但不够美观。对于那些拥有 JavaScript 的用户,您想在当前窗口的一个 lightbox 覆盖层、而不是一个新窗口中显示一个大图。清单 9 展示了相关代码(假设您的 lightbox 通过一个 更进一步,您可能拥有一系列缩略图,并想为每个缩略图打开一个包含对应大图的 lightbox。编码过程也相当简单。不要使用 IDs,而是使用一个类名;或者使用 如您所见,最终结果是一些非常干净、可读性强、且容易维护的代码。您可以将一些事件附加到多个项目,而不必使用不必要的 回页首 低调的 Ajax Web 2.0 和 Ajax 应用程序的出现给计划创建交互式 web 应用程序的 web 开发人员带来了新的挑战。使用异步 HTTP 请求来检索数据的优势很明显 — 利用 Ajax 的应用程序更具响应性、更易用、更接近传统桌面应用程序可能具有的用途(有些人可能会认为设计良好的 web 应用程序甚至比传统应用程序更有用、更容易访问)。 使用 Ajax 请求的问题是,当您需要满足没有有幸拥有 JavaScript 应用程序的用户的需求时,它会非常迅速地模糊您的预见。以一个典型的注册表为例。有许多方法可以 Ajax 化 这种表单。可以自动检查一个用户名或电子邮件地址是否已经不再使用,验证表单而无需刷新页面,甚至提交表单并动态显示一个结果。 我们以一个请求用户名和密码的简单表单为例,它发送一个异步 HTTP 请求到服务器端脚本,该脚本在处理成功后返回一个 “ok” 响应,如存在验证问题则返回一条错误消息。HTML 形式的的代码表单可能类似于清单 11。 第一个问题应该非常明显。清单 11 中的表单没有方法或动作属性,这意味着当您单击 Register 按钮时,什么也不会发生。仅仅因为您可以轻松使用 JavaScript 来通过表单的 修改后的表单看起来如清单 12 所示。 现在您可以使用这个表单并将您的 Ajax 请求发送到相同的服务器端脚本,该脚本将能够很容易地区分如何返回响应(如果 Ajax 字段不存在,则返回注册页面并报告一个错误;如果字段值为 1,则只是输出一条简单消息)。执行这个动作的 JavaScript 代码(Prototype 库提供了一点帮助)可能类似于清单 13。 在清单 13 中,您向表单的 回页首 另一个低调的 Ajax 示例 Ajax 特别有用的另一个场景是将一些大型数据集编组到一些页面中,这个过程通常称为分页(pagination)。例如,如果您拥有一个搜索结果集,而不是一次显示几百个或更多搜索结果,那么您将更有可能显示这个数据的一个更小的子集(例如 10 条记录),并向用户提供在页面之间前后移动的选项。这种情况的一个例子是 Google 的搜索引擎。在页面底部有一个在结果页面之间导航的选项。 通常,在页面之间移动的标准方法是向加载数据的服务器端脚本传递一个参数,告知它应该输出结果集的哪个页面。问题是,每当用户需要移动到另一个页面时,都必须发送一个请求到服务器,从而导致页面使用新的数据集重新加载。 使用 Ajax,您可以确保当一个新数据页面正在被提取时,用户不必观看整个页面重载,而是只使用新数据集替代部分页面。下面我们来看一个示例。 最重要的是,您需要总是满足那些没有 JavaScript 的用户的需求。为此,您需要确保您的应用程序以传统方式工作,页面使用一个新数据集刷新(见清单 14)。同样,您将使用将一个 results.php 服务器端脚本将生成一个类似于清单 14 中的 page 1 的页面,单击其中的链接可导航到第二页或者最后一页。如果您不在 page 1 上,将会看到 Previous Page 和 First Page 链接,如果您在最后一页上,则不会看到 Next page 或 Last Page 链接。您甚至可以显示一个页面列表,以便轻松跳转到某个页面。因此,如何 Ajax 化 这个分页部分,又避免对非 JavaScript 浏览器打破它呢?答案其实很简单,只需获取所有分页链接的一个引用,当链接被单击时阻止默认动作(浏览器使用链接的 然后,服务器将要么返回整个页面,要么只返回您希望使用请求的数据集替换的部分,具体情况取决于使用 Ajax 还是常规 这又是一个简单的 Ajax 示例。您肯定已经看到,以下面这种方式使用 Ajax 比较简单:渐进地增强您的应用程序,而不是使 JavaScript 成为一个强制要求。另外,通过采用更好的编码实践,您肯定会发现,以一种低调的方式使用 Ajax 比使用 JavaScript 以一种不连贯的方式来完成相同的任务要简单高效得多。 回页首 结束语 本文向您介绍了低调的 JavaScript 和渐进式增强的概念,以及下面这个设计理念:设计您的应用程序时一开始不使用 JavaScript,然后添加 JavaScript 来改善从 Javascript 获益的用户的用户体验。本文不可能是关于如何以一种低调的方式开发应用程序的一个面面俱到的参考资料。但是,如果您像我一样,花费了大量时间来开发未必以低调的方式使用 JavaScript 和 Ajax 的 web 应用程序,您可能会对接受这种 web 开发哲学将会带来的巨大好处感到非常惊喜。 参考资料 学习 获得产品和技术 讨论 标记。如果您需要链接到其他项目,则使用良好的旧式超链接;您将不能将事件附加到不适合的元素,比如
或
标记的表单字段,特别是在处理 Ajax 应用程序时。现在很容易消除在您的应用程序中必须包含一个
元素的烦恼,并使用
document.getElementById
等 DOM 方法来获取需要提交的值,然后使用一个XMLHttpRequest
来将值提交到服务器。以这种方式构建应用程序的问题是,默认事件不会受到考虑,最终结果是您的应用程序变得不太便于使用。例如,假如您拥有一个文本框和一个按钮,它们使用本节中描述的 XMLHttpRequest
来搜索您的站点,搜索过程不会使用 元素。可以预期,当您在文本框中输入您的查询并按下 Return 键时,表单将被提交,查询将被执行。遗憾的是,情况并非如此,您可能会发现自己必须努力确定如何捕获按键事件来模拟这个功能。具有讽刺意味的是,如果您一开始就使用了
元素,按下 Return 键就已经奏效了。这只是 HTML 元素提供的许多被忽略的默认行为的一个示例,而开发人员每天都在继续滥用和误用这些默认行为。
标记,我们要将一个
onclick
事件附加到缩略图并调用 lightbox 来显示大图。下面我们仔细研究这是如何发生、进而满足那些没有 JavaScript 支持的用户的需求的。
清单 8. 只使用 HTML 的解决方案
openLightbox
函数启动,该函数接受大图的 URL 作为参数):
清单 9. shownLargImage()
函数
function showLargeImage(e) {
Event.stop(e);
var link = Event.element(e).up("a");
openLightbox(link.href);
}
document.observe("dom:loaded", function(e) {
$("my_thumb").observe("click", showLargeImage);
});
rel
属性(见清单 10),就像 lightbox 脚本中常用的那样。
清单 10. 创建一系列缩略图
id
属性,或者必须以内联方式附加多个处理程序。一想到以高调的方式编写代码来解决这个问题的主意,我简直就会不寒而栗。我能够想象没有 标记,非 JavaScript 浏览器中什么也不会发生。我还能想象到
元素上的内联
onclick
处理程序,大图的 URL 作为一个参数直接传递到标记本身中。退后一步,尝试弄清我正在试图实现什么目标,这样,我就能够提供这样一个解决方案:可访问,遵守标准,拥有语义含义,并以一种渐进式方式提供简洁的 lightbox 功能。
清单 11. 简单表单
id
属性获取表单并使用 Ajax 提交表单并不意味着您应该忘记提交表单的标准方法。相反,您可以编写您的服务器端脚本来寻找表单中的一个额外字段。如果该字段无法获取,则表明请求是通过一个常规表单提交发出的,您应该返回一个完整的页面。如果该字段设置为 1,则表明请求是通过使用一个异步 Ajax 调用发出的,您应该只返回一条包含 OK
或一个错误的消息。您可以在您的 Ajax 请求中将该字段的值设置为 1,如果浏览器不具有 JavaScript 支持,这当然不会被执行。
清单 12. 修改后的表单
清单 13. 使用表单将您的 Ajax 请求提交到服务器端脚本
submit
事件添加了一个事件处理程序。这个处理程序阻止默认事件(提交表单)触发,将额外的 ajax
参数的值设置为 1,使用原始表单发出一个 ajax
请求以获取 URL 和数据。如果接收到一个成功的 HTTP 响应代码,则调用registerSuccess
函数;如果出现错误,则调用 registerFailure
函数。ajax
标记传递到服务器端脚本的概念,让它知道它正在被一个常规 GET
请求调用还是被一个 Ajax 调用调用。
清单 14. 确保应用程序支持没有 JavaScript 的用户
//HTML code
href
属性中的页面替代当前页面)触发。然后,获取 href
属性的值,将其作为 Ajax 请求的 URL。最后,添加 ajax
参数,以便服务器端脚本知道这是一个 Ajax 请求(见清单 15)。GET
请求调用它。然后,您的函数使用服务器返回的 HTML 替换 results
div 的内容,返回的 HTML 包含
结果列表和分页链接(当您在页面间移动时,这些链接可能不同)。
清单 15. Ajax 化分页部分