有限状态机是什么?

一、背景介绍
有限状态机是一种模型,用来模拟事物。事物一般有以下特点:
1)可以用状态来描述事物,并且任一时刻,事物总是处于一种状态;
2)事物拥有的状态总数是有限的;
3)通过触发事物的某些行为,可以导致事物从一种状态过渡到另一种状态;
4)事物状态变化是有规则的,A状态可以变换到B,B可以变换到C,A却不一定能变换到C;
5)同一种行为,可以将事物从多种状态变成同种状态,但是不能从同种状态变成多种状态。
二、知识剖析
重点:状态和回调函数
2.1回调函数callback
英语原生含义:
A callback is a function that is passed as an argument to another function and is executed after its parent function has completed.
回调函数的几种写法:

//异步请求回调函数
$.get('ajax/test.html',function(data){
$('.result').html(data);
});
//点击事件回调函数
$('#target').click(function(){
alert('Handle for .click() called.');
});
//数组中遍历每一项调用的回调函数
this.tabs.forEach(function(tab,index){
if(tab.selected){
this.forcustab = this.tabs[index];
}
}.bind(this));
//同步回调
function getNodes(params,callback){
var list = JSON.stringfy(params);
typeof(callback)==='function' && callback(list)
}
getNodes('[1,2,3]',function(nodes)){
//拿到nodes之后用它去做一些其他操作;
};

2.2状态机的写法
比如这样一种情况,用对象来表现状态机模型:有一个菜单元素,鼠标悬停时,菜单显示;鼠标移开时,菜单隐藏。

var fsm={
// 当前状态
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;
}
},
//callback
callback1:function(){...},
callback2:function(){...},
...
};
//执行
fsm.initialize();
...
//fsm.transition();
...

2.3 Javascript Finite State Machine 函数库
javascript-state-machine插件
以下是引入函数库之后的写法,是关于交通信号灯的模型描述:

//交通信号灯的模型描述:
var fsm =new StateMachine({
init: 'green',
transitions: [
{ name: 'warn',  from: 'green',  to: 'yellow' },
{ name: 'stop', from: 'yellow', to: 'red' },
{ name: 'ready',  from: 'red',    to: 'yellow' },
{ name: 'go', from: 'yellow', to: 'green' }
],
methods:{
callback1:function(){...},
callback2:function(){...},
...
},
error: function(){...}
});

init选项用来表示fsm对象的初始状态,transitions选项用来描述fsm对象所有状态的变化规则,每一种变化规则对应一种行为。create方法为实例的每一种行为都添加了一个方法,调用这个方法就相当于触发对象的某种行为,当对象行为发生时,对象的状态就可以发生变化。如以上例子创建的实例将拥有如下行为方法:
fsm.warn() : 调用该方法,实例状态将从'green'变为'yellow'
fsm.stop() : 调用该方法,实例状态将从'yellow'变为'red'
fsm.ready() : 调用该方法,实例状态将从'red'变为'yellow'
fsm.go() : 调用该方法,实例状态将从'yellow'变为'green'
这些方法是StateMachine根据create时配置的transitions规则自动创建的,方法名跟transitions规则里面的name属性对应,transitions规则里面有几个不重复的name,就会添加几个行为方法。同时为了方便使用,它还添加了如下成员来判断和控制实例的状态和行为:
fsm.state - 返回实例当前的状态
fsm.is(state) - 如果传入的state是实例当前状态就返回true
fsm.can(eventName) - 如果传入的eventName在实例当前状态能够被触发就返回true
fsm.cannot(eventName) - 如果传入的eventName在实例当前状态不能被触发就返回true
fsm.transitions() - 以数组的形式返回实例当前状态下能够被触发的行为列表
Javascript Finite State Machine允许为每个事件指定两个回调函数,以warn事件为例: onBeforeWarn:在warn事件发生之前触发 onAfterWarn(可简写成onWarn) :在warn事件发生之后触发。 同时,它也允许为每个状态指定两个回调函数,以green状态为例: onLeaveGreen :在离开green状态时触发 onEnterGreen(可简写成onGreen) :在进入green状态时触发。
假定warn事件使得状态从green变为yellow,上面四类回调函数的发生顺序为:
onBeforeWarn → onLeaveGreen → onEnterYellow → onAfterWarn。
还为所有的事件和状态指定通用的回调函数:
onBeforeEvent :任一事件发生之前触发
onLeaveState :离开任一状态时触发
onEnterState :进入任一状态时触发
onAfterEvent :任一事件结束后触发
三、常见问题
如何使用有限状态机?
四、解决方案
将需求模型化,划分状态和相应的触发事件与动作,利用这些构建类,控制执行。
五、编码实战
杀人游戏实例
以下是一个小demo,仅作为一个思路提供给大家,并非完整代码。

var state=storage["state"]?storage["state"]:'none';
function demo(state) {
    var fsm=new StateMachine({
        init:state,
        transitions:[
            {name:'kill', from:'none', to:'killed'},
            {name:'tosay',from:'killed',to:'testament'},
            {name:'todiscuss',from:'testament',to:'discussing'},
            {name:'tovote',from:'discussing',to:'voting'},
            {name:'toclear',from:'voting',to:'none'}
        ],
        methods:{
            onBeforetosay:function (lifecycle) {
                // 按钮变色
                $("#night-steps"+count.toString()+
                " .announcement").text(decedent[decedent.length-1].index+"号被杀手杀死,身份是"+
                    decedent[decedent.length-1].identity).attr("class","text-style");
                $(".kill").children(".triangle").css("border-right-color","#83b09a");
                $(".kill").unbind("click").css("background-color","#83b09a");
            },
            ontosay:function (lifecycle) {
                alert("请死者亮明身份并发表遗言");
                // 按钮变色
                $(".last-words").unbind("click").css("background-color","#83b09a");
                $(".last-words").children(".triangle").css("border-right-color","#83b09a");
            },
            ontodiscuss:function (lifecycle) {
                alert("玩家依次发言讨论");
                // 按钮变色
                $(".speak").unbind("click").css("background-color","#83b09a");
                $(".speak").children(".triangle").css("border-right-color","#83b09a");
            },
            ontovote:function () {
                storage.setItem("state",fsm.state);
                window.location.href="kill-vote.html?";

            }
        }
    });
    $(".kill").click(function () {
        fsm.kill();
        storage.setItem("state",fsm.state);
        window.location.href="kill-vote.html?";
    });
    $(".lastwords").click(function () {
        if(fsm.state==="killed"){
            fsm.tosay();
            storage.setItem("state",fsm.state);
        }else{
            alert("请先杀人");
        }

    });
    $(".speak").click(function () {
        if(fsm.state==="testament"){
            fsm.todiscuss();
            storage.setItem("state",fsm.state);
        }else if(fsm.state==="killed"){
            alert("请先发表遗言")
        }else{
            alert("请先杀人")
        }

    });
    $(".vote").click(function () {
        if(fsm.state==="discussing"){
            fsm.tovote();
            storage.setItem("state",fsm.state);
        }else if(fsm.state==="killed"){
            alert("请先发表遗言")
        }else if(fsm.state==="testament"){
            alert("请玩家先发言讨论")
        }else{
            alert("请先杀人")
        }

    });
    switch(state){
        case 'none':
            storage.setItem("state",state);
            return fsm;
            break;
        case 'killed':
            fsm.onBeforetosay();
            return fsm;
            break;
    }
}
demo(state);

六、扩展思考
以下是Javascript Finite State Machine 函数库上的一个demo,信号灯实例
代码演示:demo
七、更多讨论
讨论一、同步调用、回调和异步调用有什么区别?
讨论二、有限状态机有哪些优点,与普通的if判断语句相比呢?
讨论三、有限状态机还有哪些应用场景?
八、参考文献
参考一:阮一峰:Javascript与有限状态机
参考二:流云诸葛:用有限状态机的思路定义组件
参考三:Cayley的世界:关于回调函数callback

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容