JavaScript 中的有限状态机,第 1 部分: 设计一个小部件使用 JavaScript 和有限状态机开发浏览器应用程序 |
级别: 初级 Edward J Pring (pring@us.ibm.com ), Senior Software Engineer, IBM 2007 年 1 月 29 日 <!-- START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --> <!-- END RESERVED FOR FUTURE USE INCLUDE FILES-->
多年以来,Web 设计人员一直都通过在流行的 Web 浏览器内采用 JavaScript 解释器的方式来改善其网站的外观。他们的做法大都是将代码的简短片段复制到 HTML 页面中。当前,随着 Ajax 的日益流行,软件工程师也开始使用 JavaScript 来开发能在浏览器内执行的新一代的应用程序。基于浏览器的应用程序的规模不断扩大,这就相应要求采用其他执行环境成长和发展所使用的相同设计模式和开发原理。
基于浏览器的应用程序在实时环境中执行,在这种环境中鼠标、键盘、定时器、网络和程序事件都十分常见。当事件驱动的应用程序的行为取决于事件发生的顺序时,其编程就会变得非常复杂,也十分难以调试和修改。软件工程师早已开始使用 有限状态机 —— 学术领域有时又称其为离散或确定性有限自动机 —— 作为一种组织原理来开发事件驱动的程序了。 有限状态机通过用直观的表格代替复杂的逻辑为设计增加了严密性。从传统意义上讲,有限状态机对开发诸如网络驱动程序和编译器这类程序颇有帮助。有限状态机也同样有助于开发基于浏览器的应用程序。 在本系列中,您将练习开发一个样例有限状态机应用程序,来深入体验 JavaScript 语言的一些独特特性:
这些语言特性可以提供一种紧凑而简明的方式来为状态间的事件和转移组织动作,还可以提供一种巧妙的方式来兼容不同的浏览器事件模型。 样例应用程序 FadingTooltip 比内置于大多数浏览器的默认工具提示更为精致。用 FadingTooltip 小部件创建的工具提示使用动画式淡入和淡出代替突然弹出和消失,并可随光标移动。设计此行为所用的有限状态机模式使逻辑清晰透明。实现此行为所用的 JavaScript 语言特性则使源代码紧凑而有效。 本文展示了如何使用有限状态机的图、表表示设计一个动画式小部件的行为。本系列的后续文章会介绍如何在 JavaScript 内实现有限状态机的表表示以及如何处理与在流行的浏览器内进行测试和实现相关的实际问题。 当 光标暂时停留于一些可视控件 —— 比如按钮、选择器或输入字段 —— 时,时下的许多图形应用程序都能暂时显示包含相应的帮助性定义、操作说明或建议的小文本框。在早期的系统中,这些小文本框被称为 “气球帮助”,在 IBM 的一些产品中,称其为 infopop,在一些 Microsoft 产品中,其名字则是 ScreenTip。在本文,我使用的是其中更为常见的术语工具提示 。 现在一些流行的 Web 浏览器,比如 Netscape Navigator、Microsoft Internet Explorer、Opera 和 Mozilla Firefox,会为任何拥有 清单 1. 浏览器工具提示的 HTML 代码
样例页面 展示了浏览器如何呈现具有 内 置的工具提示还有很多可待提高之处,一些流行浏览器的最新版本为构建更为精致的工具提示提供了所需的 “原料”。HTML Division 元素创建了一个可在浏览器窗口的任何地方放置的提示框。通过级联样式表(CSS),您几乎可以设定框体外观的各个方面。用 JavaScript 编程实现的光标移动可以触发浏览器窗口内任意可视元素的特定动作。您还可以编制一个定时器来控制这些动作的顺序。 在 样例页面 可以找到具有这类工具提示的一些 HTML 元素。如果运行的是流行浏览器的最新版本,您就可以将更为精致的工具提示和内置的工具提示做一对比:
这 些增强的行为和外观不仅有修饰的作用,还可以提高可用性。面对有数十个或数百个元素的繁忙页面,用户很可能会错过即刻弹出的工具提示。人类的视觉系统对运 动的物体十分敏感,因而也更容易注意到淡入视野并随鼠标而动的工具提示,即使用户的注意力不在这儿也没关系。对比未格式化过的文本,图像、格式化和样式化 能更有效地传递信息。而且,这些更为精致的工具提示的所有参数都是可配置的。 本文后面的内容将着重于介绍如何将 FadingTooltip 小部件设计为一个有限状态机。本系列的后续文章会为您展示如何实现和测试这些代码。如果您急于想知道这些代码,也可以在 参考资料 部分找到到相关 JavaScript 源代码和使用这些代码的一个 HTML Web 页面的链接。
有限状态机 有限状态机对行为建模,在该模型中,对将来事件的响应取决于先前的事件。此领域已出现了大量学术著作(参见 参考资料 ),而有限状态机的实用定义却十分简单明了。有限状态机就是包含如下内容的计算机程序:
在行为由许多不同类型事件驱动以及对特定事件的响应取决于先前事件发生顺序的情况下,有限状态机最为有用。 驱动有限状态机的事件可以是计算机外部的(由键盘、鼠标、定时器或网络活动发起),也可以是计算机内部的(由本应用程序的其他部分或其他应用程序发起)。 状态是记起先前事件的一种方式,转移则用来组织对将来事件的响应。其中的一个状态必须要被指派为初始状态。结束状态可有可无,FadingTooltip 小部件就没有结束状态。 有限状态机的两种常见表示为:
用 有限状态机开发事件驱动程序比一般的过程式编程要复杂一些;一般来说,需要更多的规则,尤其是更多的设计精力。如果处理得当,有限状态机可以使代码简单、 测试迅速、维护轻松。但是,即便如此,有限状态机的复杂性使其并不能适合所有事件驱动的程序的开发。例如,当事件的种类不多或事件触发的动作总是相同时, 进行额外的开发可能会得不偿失。
有限状态机是事件驱动的,需要在它们的运行时环境将其与其相关的事件挂接起来。这可通过事件处理程序 实现,事件处理程序是一些可插入到运行时环境的小的代码片段,一旦特定事件发生,这些处理程序就会执行。事件处理程序执行时,需要获得如下一些基本信息:
JavaScript 十分适合于构建事件驱动的有限状态机。事实上,JavaScript 有点太过适合 —— 它有三种挂接事件的方式。每种事件模型 都很直观明了,但程序必须实现所有三种模型以确保它们可以运行于所有流行的浏览器之上。事件的上下文在其中两个事件模型内被直接传递给事件处理程序;对于另外一个模型,JavaScript 函数闭包允许事件的上下文被包裹进其事件处理程序。 JavaScript 提供一种对象模型 ,对象模型是 Java 和 C++ 程序员所熟知的,它也可用来对有限状态机的变量和方法进行编码。而且,JavaScript 关联数组还允许直接对有限状态机的二维表进行编码。
有限状态机的基本要素是它所响应的事件及事件间的状态。设计必须考虑到每个可能状态的每个可能事件:
我以 图 1 所示的一个图形来开始设计的过程,图中气球形圆圈所示的是状态,连接这些圆圈的箭头线代表的是转移。最终获得的是一张表,如 图 4 所示,在该表的标题行和标题列分别列出了事件和状态。表中的一些单元格列出了当特定事件在特定状态发生时所要执行的动作,其它一些则表示在该状态下此事件不能发生。 通 常,需要反复执行此设计过程才能获得正确的图和表。对具有多个事件和状态的有限状态机,这个过程可能会十分乏味,每次重复都需要遵守一定的原则来系统地处 理表中的每一个单元格。这迫使您不得不考虑在每个可能的情况下您所想要的动作。您可能会发现还可以进一步完善这些行为,也可能会发现所需的状态较预计的要 多(或少),甚至会发现您必须重新整理单元格间的这些动作以正确定义每种情况下的行为。 这种设计有限状态机的系统过程虽然有些乏味但却十分值得。图 4 所示的完成后的表给出了此行动的所有逻辑,并可被直接转换为代码(参见 actionTransitionFunctions 源代码)。
要设计 FadingTooltip 小部件,您需要了解 JavaScript 的一些功能。在严谨设计的原则指导下,我只在这里给出基本的设计思想,而将具体的实现留待本系列后续文章中介绍。 当光标经过页面中的 HTML 元素时,所有流行的浏览器都能将事件传递给 JavaScript 代码。这些事件是 mouseover 、mousemove 和 mouseout ,分别代表光标已经移至、移上和移出 HTML 元素。浏览器用这些事件传递光标当前位置。当事件发生时,可用 JavaScript 编程动态创建 HTML Division 元素,用文本、图像和标记填充这些元素并将其定位到光标附近。 浏览器并没有原生的淡入和淡出函数,但可以通过改变 Division 元素的透明度(实际上是不透明度,透明度的反义词)来模拟这些函数。 JavaScript 有两类定时器:一次定时器在超时时生成 timeout 事件;重复断续器定期生成 timetick 事件。FadingTooltip 小部件需要这两种定时器。
首先回顾一下想要从 FadingTooltip 小部件获得的基本行为。当光标从特定的 HTML 元素上移过的时候,您可能想让此小部件等待光标在该元素上暂停。如果可以如此,之后您可能又想让此小部件将工具提示淡入,显示一会后再淡出。 有限状态机将需要响应以下事件:
您 将需要设计状态机在事件间等待的一些状态。需要调用小部件的初始状态 Inactive,小部件在该状态下等待被 mouseover 事件激活。小部件在 Pause 状态下等待直到 timeout 事件指示光标已经在 HTML 元素上停留了足够长的时间。之后在用 timetick 事件动画式淡入的同时,小部件会在 FadeIn 状态下等待,继而又会在 Display 状态等待另一个 timeout 事件。最后,在用更多 timetick 事件动画式淡出的同时,小部件会在 FadeOut 状态下等待。小部件转回到 Inactive 状态,在此状态下等待另一个 mouseover 事件。 图 1 是此过程相应的图形表示,其中的气球形圆圈代表状态,连接圆圈的箭头线代表转移,箭头线上的标注代表事件。双层边界的圆圈代表初始状态。 图 1. 状态图的初始设计 FadingTooltip 小部件必须针对它处理的每个事件采取动作:
图 2 在触发这些动作的事件之下列出了这些动作。 图 2. 在初始状态图的事件下追加动作
上述的状态图是设计有限状态机的一个很好的开始。但表形式更适合于完成设计,原因是表可以给出事件和状态的所有组合以供参考。 要将状态图转换成状态表,可以在行标题内填上事件名,在列标题内填上状态名。这些名字的顺序是任意的;我在第一行的开始位置放入了初始状态,在第一列的开始位置放入了初始事件,随后将动作和每一事件的下一状态复制到表中适当的单元格内,如 图 3 所示。 图 3. 与初始状态图对应的初始状态表
要完成有限状态机的设计,需要顾及表中的每一个空单元格。您需要为每个单元格做这样的考虑:该事件是否可以发生在该状态,如果可以,小部件在这种情况下将采取什么动作,下一个状态又将是什么。这虽然有些乏味,但却是设计过程的必需部分。 考 虑单元格的顺序先后关系不大。通常在设计过程中需要多次重复此步骤,反复考虑每个单元格,不时地修改其内容,而且每次的考虑顺序都会有所不同。另外随着设 计的不断深入,添加(或删除)状态、做进一步的修改也十分常见。在这里,我将跳过这些反复过程,着重总结如何通过依次查看每个状态和事件来获得最终的结果 表。
图 4 显示了所有这些动作和转移。剩下的空白单元格应标记为“不应发生”。 图 4. FadingTooltip 小部件设计后的状态表 有限状态机的状态表总是能转换回状态图,因为二者是等价的。图 5 显示了完整的状态表对应的状态图。 图 5. FadingTooltip 小部件设计后的状态图
完成状态表和状态图之后,很有必要对它再进行一次回顾来收集状态机在两事件间需要记录的变量以便状态机能够执行不同的单元格内的相应动作。有限状态机需要 清单 2 中所示的状态变量。 清单 2.初始的状态变量列表
虽然 JavaScript 变量本身不区分类型,但变量所包含的值是区分类型的(这就是说,任何类型的值都可以赋给变量)。根据这一原则,我列出了状态变量名并在注释部分给出了希望赋给这些变量的值的类型。
有了完整的状态表和状态变量之后,就可以实现有限状态机了。本系列的下一篇文章将重点介绍实现过程,敬请关注。不过请记住开发是个反复的过程,有时您可能需要返回设计阶段。 下载
学习
获得产品和技术
讨论
|