单页界面和 AJAX 模式
前端之家收集整理的这篇文章主要介绍了
单页界面和 AJAX 模式,
前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
单页界面和 AJAX 模式
Dino Esposito
与当今构建的绝大多数 Web 应用程序所采用的开发模式相比,AJAX 对 Web
解决方案架构师而言意味着一种模式转变。它立足于一些新的原则和规则来解释基于 Web 的系统的行为,并要求采用一些新的算法来实现它们。
AJAX 背后的主要原则是
用户将纯数据发送到 Web 服务器,然后接收更多的纯数据。
AJAX 的第二个原则是
用户自行协调操作,这将略过主机浏览器及其单
页面请求/响应机制。
第三个 AJAX 原则是客户端
代码利用从服务器接收到的纯数据来全面负责对
用户界面的更新。
本专栏将为那些准备完全摆脱防御性 AJAX 实现(表现为部分呈现)的开发人员奠定基础。部分呈现是一种在处于 Web 窗体体系结构中时仍可实现某些 AJAX
功能的途径。AJAX 模式基于一种全新的原则,这种原则要求采用新的设计模式。
AJAX 模式的影响
ASP.NET 部分呈现是一种非常智能的
添加内容,它属于传统的 Web 窗体回发模型。简而言之,使用部分呈现的
页面其回发体系结构和
页面生命周期与非 AJAX
页面中的完全相同(请参见
图 1)。不同之处在于位于客户端的侦听器只阻止浏览器的默认操作(表单提交),并用 XMLHttpRequest 引导的 HTTP 请求来替代它(请参见
图 2)。此
方法不但可以节省
用户的整页刷新时间,同时还可以节省开发人员在新体系结构和新模式培训方面所花的时间。
图 1
传统的整页回发操作(单击该图像获得较大视图)
图 2
AJAX XMLHttpRequest 部分呈现(单击该图像获得较大视图)
毋庸讳言,部分呈现在
当前页面的上下文中提供了图形化优势。但是,如果您的应用程序是围绕一组独立的
页面设计的,那么从一个
页面到下一个
页面的过渡仍需要整页加载。部分呈现非常适合于替换内部
页面回发,例如,对网格进行
分页、变更选择后调整
用户界面或就地编辑记录。
由于部分呈现只是一种更智能形式的
页面回发,因此它具有与 Web 窗体模型相同的体系结构限制。例如,每个浏览器窗口只能有一个待决请求。对于当前的 Web 应用程序(提交表单、
获取新
页面)模型而言,此限制可能不会产生太大问题。
但是,随着您对 AJAX 认识的拓展,自然而言就会期望
用户可以同时执行多个活动。仅依靠部分呈现,
用户是无法同时执行两个异步操作并将其完成的。
部分呈现只能每次执行一个请求操作以保持视图状态的一致性(一致性仍是模型不可或缺的组成部分)。这严重违背了 AJAX 中的第一个要求:异步。使用部分呈现(带外执行),可以实现 JavaScript 驱动的回发,但每次只能有一个。如果在前一个操作完成之前触发了第二个操作,则待处理操作将被中止,以便新操作可以继续执行。可通过编程方式将这一后进得胜策略改为先进得胜模型,从而使当前操作仍保持活动状态,而新操作将被系统取消 — 实质上这仍然是每次只能执行一个操作。
单页界面模型
为了充分利用 AJAX,必须在单一
页面中包含其中的全部
功能,或者是至少包含大部分
功能。这称作单页界面 (SPI) 模型。在 SPI 模型中,浏览器与 Web 应用程序的所有交互只能在一个
页面的范围内进行。此
方法对 Web 来说是一种创新,但对于 Windows
®和桌面开发却并不陌生。SPI 其实模型就像是具有主(并且唯一)窗口的 Windows 应用程序一样。
在 SPI 模型中,主
页面是可以独立加载、更新和替换的一些可视元素的组合(请参见
图 3)。通过这种方式,可以不必在每次
用户操作后重新加载整个
页面。在任何时候,都只
显示与应用程序当前阶段相关的可视元素和
内容。其他所有
内容均被隐藏;但只要应用程序流程中需要用到它,它就会
显示出来。
图 3
页面中的单页界面元素(单击该图像获得较大视图)
SPI 模型自然会启用许多交互性很强的
功能,其中
包括就地编辑、上下文相关的
用户界面、即时
用户反馈
提示以及异步操作等。但 SPI 模型并不仅仅具有出色的
性能或响应能力,它的主要优势在于针对
用户体验方面做的重大改进。
不过,即使它拥有如此多的优点,您还是应该清楚,使用 SPI 模型设计新应用程序是一项具有挑战性的工作,因为没有任何现成的模式和最佳实践。目前的结论是有一种使用 AJAX 的简单
方法,它适合于众多公司和多种情况。而当前主流的 AJAX 使用
方法不太便利。
为了构建纯 AJAX 应用程序,您需要一个良好的
用户界面小组件库来提供各种
效果和特殊行为。您需要一个
内容丰富而又可以
自定义的文档对象模型 (DOM),此模型使用标准的万维网联盟 (W3C) DOM 作为其基础引擎,但它允许您定义自己的特定应用程序的模型。最后,您还需要一个服务器和客户端框架,以便轻松有效地开发
用户界面和源
代码脚本。最好还准备一些用于调试和测试所有这一切的工具。
Microsoft 和其他一些供应商提供了部分这样的工具。有许多 UI 小组件库可供您选择,此外还有一些供应商,它们生产各种用来定义其各自客户端对象模型的控件。而真正有帮助的则是改进后的专门设计用作 Web 窗体备选模型的 AJAX 框架,它的灵感源自 SPI 模型。
其中一个此类替代编程模型是模型视图控制器 (MVC) 框架,在 ASP.NET 3.5 扩展库中可以找到它。但就目前来看,它并没有太多值得 AJAX 借鉴的
内容,虽然它不会阻止任何 AJAX
功能的实现,但它也似乎没有发展成为 SPI 模型的打算。不过时间会证明一切的。
在 SPI 模型中,主
页面指向同一应用程序中的 HTTP 端点。它执行远程
代码,但不会重新加载整个
页面。而且,它还会使用
生成 HTML 和脚本的控件来更
新用户界面。这些控件非常智能,可以
生成它们需要的许多 JavaScript
代码。例如,假定有一个用来预订航班的表单,其中可能有两个参数需要
搜索:时间或成本。如果
用户对便宜的航班更感兴趣,那么您就不必
显示时间下拉列表。如果
用户需要在准确的时间起飞,那么您就必须调出
显示时间的 HTML。
很明显,只需要一些 JavaScript 就可以轻松实现这种
显示/隐藏技巧。但是现在,这些
代码需要由
页面开发人员来负责编写。ASP.NET AJAX 控件工具包 (
asp.net/AJAX/AjaxControlToolkit/Samples/CollapsiblePanel/CollapsiblePanel.aspx) 中提供的控件(如 CollapsiblePanel 控件)也可以帮助实现此操作。
单页界面模型的缺点
虽然 SPI 模型(及其包含的整个 AJAX 模式)带来了交互性更强的
用户体验,但同时也引发了诸多问题,例如可
搜索性、历史管理、可访问性和脱机
支持等。多年以来,一直使用永久
链接来跟踪 Web
页面。
搜索引擎只需将一组关键字映射到一个或多个 URL 即可构建其业务。此模型的工作前提是假设 Web 应用程序中每个状态都对应一个
页面和一个不同的 URL。
如果 SPI 模型处在 AJAX 中,则此假设不再有效。如果所有一切(或大部分)都发生在同一
页面中,则没有 URL 转换来
标记不同的状态和不同的
站点内容。因此,没有将
内容(和关键字)与唯一 URL 相关联的简单
方法。SPI 模型的这一特性会影响可
搜索性以及历史管理(例如,使用“后退”和“前进”按钮的能力)。
浏览器历史记录最终可能会变为过时的概念,它们是传统静态 Web 模型的搭档。但是您必须要知道,
用户对这些按钮实在是太熟悉了,简单地禁用它们并不是一个可行的办法。
可访问性是 AJAX 应用程序的另一个大问题。大多数流行的屏幕读取器在处理通过 DOM 脚本
生成的任何
内容时都面临很大的困难。按照设计,所有形式的 AJAX(
包括部分呈现)都严重依赖于 DOM 脚本。因此,您需要通过其他途径来
解决可访问性问题。
Web Content Accessibility Guidelines (WCAG) 中的第 508 节建议,只要
页面利用脚本语言来
显示内容或创建可视元素,它所提供的替代
功能文本就应该可以通过辅助技术加以访问。为符合此建议,需要使用 <noscript>
标记。当前,大多数 AJAX 框架都在
页面内部进行动态更新,而不更新 <noscript>
标记中包含的静态信息。
可访问的富 Internet 应用程序
此问题看起来好像是双重的,既涉及 AJAX 应用程序,又涉及屏幕读取器背后的技术。一方面,屏幕读取器要了解一些客户端事件,如 onclick、keypress 和 readystatechange 等。在屏幕读取器中弄清了这些事件后,便可以至少将第一个可访问层
添加到大量基于 AJAX 的应用程序中。
另一方面,如果屏幕读取器超出了 DOM 中的初始
页面加载事件范围,它们通常不会发出任何新信息。但您可以使用一些技巧让屏幕读取器知道有些
内容已经发生了变化。最有效的一招就是在更新后的 DOM 树的根目录将 tabindex 设置为 -1。但这只是一个小技巧,可访问性的
解决需要更为通用的 AJAX
解决方案。
其中的一种可行
解决方案是 W3C 正在制定的被称为“可访问富 Internet 应用程序”(ARIA) 的新标准,它主要由 HTML
标记的特定读取器扩展组成。一些比较流行的客户端 AJAX 库已经开始
支持某些 ARIA
功能了。但一般来说,这并不仅仅是有关 AJAX 可访问性兼容标准的可用性问题。它还常常涉及服务器控件实际
生成的
内容(
标记和脚本)。
对于脱机应用程序是怎样的情况?许多开发人员认为脱机 AJAX 应用程序不可能实现,因为 AJAX 应用程序仍然完全基于 Internet。在我看来,这种说法虽然并非完全
错误,但也有点过于简单化了。现在,几乎所有 Web 应用程序(AJAX 和非 AJAX)都基于 Internet 或 intranet。
但是,非 AJAX 应用程序的确可以脱机工作。例如,对于历史记录管理,启用脱机导航的魔力完全在于浏览器。在传统的 Web 应用程序中,每个 HTTP 请求都由浏览器来管理。如果没有可用连接(在引发 HTTP
404 错误之前),浏览器会在其本地
页面缓存中进行查找。
AJAX 应用程序不同于传统的 Web 应用程序,它使用 XMLHttpRequest 对象而非浏览器引擎来发送 HTTP 请求。对于
支持脱机情况的 AJAX 应用程序,您只需为 XMLHttpRequest 对象赋予访问浏览器缓存的权限或赋予创建并管理其自身已访问
页面的缓存的能力即可。某些 AJAX 框架已开始引入此
功能。但这并非是一件容易的事,因为它涉及从 JavaScript
代码访问磁盘。
AJAX 模式概述
下一代基于 Web 的应用程序将直接面向 AJAX,而且会更多直接面向富 Internet 应用程序 (RIA)。起初,引领这一革新版本的是以下三大类应用程序:基于 HTML 的传统
站点、为了将多个系统集成到一个基于 Web 的前端而构建的混合应用程序、胖客户端。
对于第一个类别,部分
用户界面加载(部分呈现)是实现 AJAX 的一种简便
方法,它对现有
代码和技能的影响非常小。
而混合程序的概念本身未必适合于 AJAX 和改进的
用户交互。它只是一种从各种来源收集数据并将其一并放入一个统一而又一致的
用户界面中的
方法。此操作也可以在传统的服务器对服务器的情况下执行。但是坦率地说,AJAX 可以使其更加简单有效。从 AJAX 角度看,混合应用程序需要标准的数据序列化格式(整合)、用于通过脚本
调用远程服务的轻量级框架、可更新的 DOM,可能还有一些具有方便的编程模型的富可视控件。
使用 AJAX 构建胖客户端是一个最严峻的挑战。胖客户端可以是分布式企业系统的前端,也可以是业务线应用程序的表现逻辑层。它还可以是 IT 部门决定作为 Web 应用程序而公开的独立应用程序。这些应用程序无论是发布到 Internet 上还是限制在 intranet 内,都需要常规桌面 UI 所具有的丰富性和速度。
与 Windows 开发相比,在交互性和响应性方面 Web 开发都是一种倒退。利用 AJAX,您最终可以使用其中的工具(和环境条件)向本质上不同的模型演化。但其中存在着不容忽视的折衷。一方面,有数百万的
用户和开发人员已习惯了使用旧的 Web 及其历史模式、脱机导航、收藏夹、单一操作、
页面转换和永久
链接等。另一方面,您采用的却是 AJAX 及其并行操作模式和单一
自动更
新用户界面
功能。在这种情况下,对于
用户任务,AJAX 模型需要真正的恢复模型,而不仅仅是使用浏览器的历史记录和
页面导航
功能。
要对 SPI 模型编写
代码,需要使用一组新的设计模式。
图 4列出了一些最流行的 AJAX 模式。正如您所看到的,其中大部分都侧重于
用户界面技术和排列,但您也应该注意到,此列表中并不
包括一些已在 Microsoft
®AJAX 客户端库中实现的 AJAX 流行模式和实践。例如,如果您使用 ASP.NET AJAX,则不需要 AJAX 存根、跨浏览器 JavaScript 模型或
调用跟踪模型,因为所有这些
功能都是预置的。
Figure4一些 AJAX 模式
模式 |
目标 |
浏览器端模板化 |
此模式建议使用 HTML 模板,它们将使用从远程 HTTP 端点检索的数据动态填充。此模式建议您设置自己的模板层,而不要基于每个请求动态为数据重新生成 HTML 布局。此模式是 HTML Message 模式的替代方案。 |
跨域代理 |
此模式对可访问的、公开提供的服务运行服务器到服务器连接,并将数据传回客户端。在客户端浏览器中,不允许 AJAX 应用程序连接到页面域外部的任何 URL。但是,同一域内的本地代理可以轻松地从任何位置获取数据并将其返给调用方。 |
检测信号 |
由于许多 AJAX 应用程序可能会在客户端执行大量操作而不传回信息,因此可能需要通知服务器指定客户端仍处于活动状态。此模式建议客户端应用程序要定期上载“检测信号”消息,以指示应用程序仍在载入状态并在浏览器中正常运行。 |
HTML 消息 |
远程 HTTP 端点通常会返回要集成到现有 DOM 中的客户端 JavaScript Object Notation (JSON) 数据。此任务只能通过 JavaScript 来完成。但是,如果客户端代码特别复杂或考虑到性能问题,您可能希望从服务器返回 HTML(数据和布局)而不是纯数据。 |
微链接 |
AJAX 主要用于在同一页面内执行大量活动。那么如何引用外部内容(即在传统 Web 应用程序中会转到不同页面的内容)?您需要一种页内超链接或称为微链接。微链接是对标记块的引用,它通过服务器调用进行检索然后再插入到页面中。微链接可以是 HTTP 端点,也可以是 JavaScript 命令对象的方法。 |
点播 JavaScript |
这是流行的延迟加载模式的 JavaScript 版本,通常用于数据访问层。在页面初始化过程中下载所需的全部 JavaScript 可能会对性能产生影响,从而拖慢整个进程。通过采用按请求来加载 JavaScript 文件的方法,可以使页面获得更快的加载速度,同时还不会对功能产生影响。 |
页面安排 |
由于大部分应用程序的活动都发生在同一个页面中,因此您需要更新页面的内容并随着上下文内容的变化来显示最新信息。此模式只建议您使用 DOM 来添加/删除或显示/隐藏元素以反映其状态的转换。 |
定期刷新 |
浏览器会定期安排请求以获得最新信息,并用它来刷新用户界面。 |
弹出框 |
此模式代表 Web 版本的模式/无模式 Windows 对话框。弹出框由一些 HTML 内容组成,它们显示在现有内容的前面,显示时间可以很短,也可以一直显示直到用户将其取消。 |
预取模式 |
此模式建议预测最可能的用户操作并预先获取所需的数据。实现此模式需要付出一定的代价:毕竟它只是猜测,有时可能会预测错误。尽管可以有效地提高感知性能,但如果实现效果不佳或者由于严重的服务器带宽损耗致使达不到理想情况,则此模式也可能会带来性能损失。 |
进度指示器 |
此模式用来监视服务器操作的进度。其思路是,服务器操作将其自身的进度写入一个共享位置,而客户端监控服务则可在进度刷新时进行读取。 |
提交限制 |
AJAX 的其中一个潜在缺陷是在单位时间内可能会生成过多的服务器请求。如果出现这种情况,则说明存在着明显的可伸缩性问题。此模式建议您使用定时器将数据定期上载到服务器和本地缓存或队列中,以将请求累积起来。 |
超时 |
如果从客户端执行一些重量级操作(如流式或定期刷新),则要想保证每个连接的客户端都真正使用此应用程序会有一些困难。此模式建议您使繁重操作超时,在用户提出明确请求时再恢复。 |
唯一 URL |
对于反映不同状态的应用程序的不同部分,此模式允许您为其分配不同的 URL。此模式通常用于支持 AJAX 应用程序中的历史记录。 |
虚拟工作区 |
服务器需要尽快响应请求,但由于带宽原因,它可能不必返回所有可用的数据。此模式建议构建一个虚拟用户界面,即使数据在客户端只存在一小部分,也可以实现使所有数据都可用的目的。应用程序会负责根据需要下载数据并将其缓存到本地。 |
您还应注意,在
图 4列出的模式中(一般为 AJAX 模式),参考模式要多于设计模式。它们指出了常见的操作执行
方法,但并非它们都是设计问题。
在
图 4中,我简要概述了每个模式的要点。在本专栏的剩余部分,我要对其中的部分模式进行深入的分析。在以后的专栏中,我将回到其他一些重要模式,讨论其驱动力并演示一些执行
方法。(要了解有关 AJAX 模式的详细信息,可以访问一个非常出色的网站
ajaxpatterns.org。)
唯一 URL 模式
URL 是 Web 的基础。
用户可以将中意的 URL 保存下来以供将来参考、可以按照 URL 所指开始新的
内容体验,此外还可以使用 URL 回到先前的状态。在 AJAX 和 SPI 模型中,应用程序可以在单个 URL 中完成许多任务。这将使 Web 体验的核心支柱面临彻底的改变,即:应用程序的离散状态是由不同的 URL 来标识的。
浏览器会在
用户浏览时构建其各自的 URL 缓存。但如果使用 AJAX,许多操作都不通过浏览器,因此不会被缓存到访问过的 URL 列表中(在此列表中可驱动“后退”和“前进”
菜单)。另一方面,客户端浏览器不会提供将 URL
添加到列表中的 JavaScript
代码编程模型。在现有列表中,浏览器对象模型只会提供向前和向后的导航
方法。
“唯一 URL”模式为每个重要的应用程序状态都分配一个唯一的、含义鲜明的 URL。例如,如果
用户在 AJAX
页面中通过单击来编辑某个值,则新 URL 会被
添加到浏览器缓存中,即使此操作是通过 XMLHttpRequest 在同一
页面中执行的。
可使用以下 JavaScript 来更改 URL 而无需重新加载
页面:
window.location.hash = stateInfo;
此
代码的作用是将以 # 作为前缀的片段
添加到 URL 中,如下所示:
http://www.contoso.com/shopping.aspx#edit-1234
但是,要执行的操作远不止捕获 URL 这样简单。如果浏览器被定向到基于哈希值的 URL,它首先会加载主 URL,然后再查找具有此哈希
名称的
页面段。在 AJAX 上下文中,哈希
名称并不指向实际的
页面段,而是指向代表当前状态的特定于应用程序的信息。例如,edit-1234 可能表示您正在编辑的项目其 ID 是 1234。实际格式完全取决于您。
如果浏览器找不到适当的段,则它将忽略 URL 哈希值。这样,
用户会加载该
页面,但可能不是以预期的应用程序状态。此外还需要另一个技巧。您应
截取页面的 onload 事件、分析 URL、
提取哈希值并运行将
页面置于期望状态所需的 JavaScript
代码,如下所示:
window.onload = function() {
checkAndParseURL();
}
checkAndParseURL() {
var state = window.location.hash;
restorePage(state);
}