​​有限状态机​​(Finite-state machine)是一个非常有用的模型,可以模拟世界上大部分事物。


  简单说,它有三个特征:

* 状态总数(state)是有限的。
* 任一时刻,只处在一种状态之中。
* 某种条件下,会从一种状态转变(transition)到另一种状态。

  它对JavaScript的意义在于,很多对象可以写成有限状态机。

  举例来说,网页上有一个菜单元素。鼠标悬停的时候,菜单显示;鼠标移开的时候,菜单隐藏。如果使用有限状态机描述,就是这个菜单只有两种状态(显示和隐藏),鼠标会引发状态转变。

  代码可以写成下面这样:




1


2


3


4


5


6


7


8


9


10


11


12


13


14


15


16


17


18


19


20


21


22


23


24


25


26


27


28


29




​var​​ ​​menu = {​


 


​// 当前状态​


​    currentState: ​​​​'hide'​​​​,​


 


​// 绑定事件​


​    initialize: ​​​​function​​​​() {​


​var​​ ​​self = ​​​​this​​​​;​


​      self.on(​​​​"hover"​​​​, self.transition);​


​    },​


 


​// 状态转换​


​    transition: ​​​​function​​​​(event){​


​switch​​​​(​​​​this​​​​.currentState) {​


​case​​ ​​"hide"​​​​:​


​this​​​​.currentState = ​​​​'show'​​​​;​


​          doSomething();​


​break​​​​;​


​case​​ ​​"show"​​​​:​


​this​​​​.currentState = ​​​​'hide'​​​​;​


​          doSomething();​


​break​​​​;​


​default​​​​:​


​          console.log(​​​​'Invalid State!'​​​​);​


​break​​​​;​


​      }​


​    }​


 


​  };​



  可以看到,有限状态机的写法,逻辑清晰,表达力强,有利于封装事件。一个对象的状态越多、发生的事件越多,就越适合采用有限状态机的写法。

  另外,JavaScript语言是一种​​异步操作​​特别多的语言,常用的解决方法是指定回调函数,但这样会造成代码结构混乱、难以测试和除错等问题。有限状态机提供了​​更好的办法​​:把异步操作与对象的状态改变挂钩,当异步操作结束的时候,发生相应的状态改变,由此再触发其他操作。这要比回调函数、事件监听、发布/订阅等解决方案,在逻辑上更合理,更易于降低代码的复杂度。

  下面介绍一个有限状态机的函数库​​Javascript Finite State Machine​​。这个库非常好懂,可以帮助我们加深理解,而且功能一点都不弱。

  该库提供一个全局对象StateMachine,使用该对象的create方法,可以生成有限状态机的实例。




1




​var​​ ​​fsm = StateMachine.create();​



  生成的时候,需要提供一个参数对象,用来描述实例的性质。比如,交通信号灯(红绿灯)可以这样描述:




1


2


3


4


5


6


7


8


9


10


11


12




​var​​ ​​fsm = StateMachine.create({​


 


​    initial: ​​​​'green'​​​​,​


 


​    events: [​


​      { name: ​​​​'warn'​​​​,  from: ​​​​'green'​​​​,  to: ​​​​'yellow'​​ ​​},​


​      { name: ​​​​'stop'​​​​, from: ​​​​'yellow'​​​​, to: ​​​​'red'​​ ​​},​


​      { name: ​​​​'ready'​​​​,  from: ​​​​'red'​​​​,    to: ​​​​'yellow'​​ ​​},​


​      { name: ​​​​'go'​​​​, from: ​​​​'yellow'​​​​, to: ​​​​'green'​​ ​​}​


​    ]​


 


​  });​



  交通信号灯的初始状态(initial)为green,events属性是触发状态改变的各种事件,比如warn事件使得green状态变成yellow状态,stop事件使得yellow状态变成red状态等等。

  生成实例以后,就可以随时查询当前状态。




1


2


3


4




​* fsm.current :返回当前状态。​


​* fsm.is(s) :返回一个布尔值,表示状态s是否为当前状态。​


​* fsm.can(e) :返回一个布尔值,表示事件e是否能在当前状态触发。​


​* fsm.cannot(e) :返回一个布尔值,表示事件e是否不能在当前状态触发。​



  Javascript Finite State Machine允许为每个事件指定两个回调函数,以warn事件为例:




1


2




​* onbeforewarn:在warn事件发生之前触发。​


​* onafterwarn(可简写成onwarn) :在warn事件发生之后触发。​



  同时,它也允许为每个状态指定两个回调函数,以green状态为例:




1


2




​* onleavegreen :在离开green状态时触发。​


​* onentergreen(可简写成ongreen) :在进入green状态时触发。​



  假定warn事件使得状态从green变为yellow,上面四类回调函数的发生顺序如下:onbeforewarn → onleavegreen → onenteryellow → onafterwarn。

  除了为每个事件和状态单独指定回调函数,还可以为所有的事件和状态指定通用的回调函数。




1


2


3


4




​* onbeforeevent :任一事件发生之前触发。​


​* onleavestate :离开任一状态时触发。​


​* onenterstate :进入任一状态时触发。​


​* onafterevent :任一事件结束后触发。​



  如果事件的回调函数里面有异步操作(比如与服务器进行Ajax通信),这时我们可能希望等到异步操作结束,再发生状态改变。这就要用到transition方法。




1


2


3


4


5


6




​fsm.onwarn = ​​​​function​​​​(){​


​    light.fadeOut(​​​​'slow'​​​​, ​​​​function​​​​() {​


​      fsm.transition();​


​    });​


​return​​ ​​StateMachine.ASYNC;​


​  };​



  上面代码的回调函数里面,有一个异步操作(light.fadeOut)。如果不希望状态立即改变,就要让回调函数返回一个StateMachine.ASYNC对象,表示状态暂时不改变;等到异步操作结束,再调用transition方法,使得状态发生改变。

  Javascript Finite State Machine还允许指定错误处理函数,当发生了当前状态不可能发生的事件时自动触发。




1


2


3


4


5


6


7




​var​​ ​​fsm = StateMachine.create({​


​// ...​


​    error: ​​​​function​​​​(eventName, from, to, args, errorCode, errorMessage) {​


​return​​ ​​'event '​​ ​​+ eventName + ​​​​': '​​ ​​+ errorMessage;​


​    },​


​// ... ​


​  });​



  比如,当前状态是green,理论上这时只可能发生warn事件。要是这时发生了stop事件,就会触发上面的错误处理函数。

  Javascript Finite State Machine的基本用法就是上面这些,更详细的介绍可以参见它的​​主页​​。