javascript实现有限状态机
1、状态机描述
简单说,有限状态机是一种模型,模型都用来模拟事物,能够被有限状态机这种模型模拟的事物,一般都有以下特点:
1)可以用状态来描述事物,并且任一时刻,事物总是处于一种状态;
2)事物拥有的状态总数是有限的;
3)通过触发事物的某些行为,可以导致事物从一种状态过渡到另一种状态;
4)事物状态变化是有规则的,A状态可以变换到B,B可以变换到C,A却不一定能变换到C;
5)同一种行为,可以将事物从多种状态变成同种状态,但是不能从同种状态变成多种状态。
2、思考实现
参照已有的实现功能 https://github.com/jakesgordon/javascript-state-machine/
实现思路
1、单个页面会有多个UI,每个UI可拥有独立的一个状态机,因此用工厂模式的思路实现
2、状态切换间应该有多个时机传回调函数:onbefore->onleave->on[Event]->on[state]->onenter[State]->onafter[event]
3、留个外部调用的一些方法接口is(判断当前状态是不是此state);can(判断状态十分能转换);cannot;transitions(能转换的状态);
代码如下
1 var sm = (function () { 2 function _StateMachine(config) { 3 this.state = config.initial||'unknow'; 4 this.events = config.events; 5 this.callbacks = config.callbacks||{}; 6 this.error = config.error || function(name, from, to, args, error, msg, e) { throw e || msg; }; 7 this._hashEvent = {} 8 this.init(); 9 } 10 var __PREFIX = ['onbefore','onleave','onenter','onafter']; 11 _StateMachine.prototype.SYNC = 'sync'; 12 _StateMachine.prototype.init = function () { 13 this._initHashEvent(); 14 this._buildEvent(); 15 } 16 _StateMachine.prototype._initHashEvent = function () { 17 for (var i = this.events.length - 1; i >= 0; i--) { 18 var e = this.events[i]; 19 if (!this._hashEvent[e.name]) { 20 this._hashEvent[e.name] = [] 21 } 22 this._hashEvent[e.name].push({ 23 from:e.from, 24 to:e.to, 25 name:e.name 26 }) 27 } 28 } 29 _StateMachine.prototype._buildEvent = function () { 30 var self = this; 31 for(var i in this._hashEvent){ 32 this[i] = (function (i) { 33 return function (arg) { 34 var event = this._getEvent(this._hashEvent[i]); 35 if (JSON.stringify(event) == '{}') { 36 return false; 37 } 38 this.cacheEvent = event; //用于异步调用 39 var fn = event.name; 40 41 42 this.callbacks['onbefore'+fn]&&this.callbacks['onbefore'+fn](this,event.name,this.state,event.state,arg); 43 44 if(this.callbacks['onbefore'+fn]){ 45 var beforeValue = this.callbacks['onbefore'+fn](this,event.name,this.state,event.state,arg); 46 } 47 if(beforeValue == false){ 48 return false; 49 } 50 if(this.callbacks['onleave'+this.state]){ 51 var leaveValue = this.callbacks['onleave'+this.state](this,event.name,this.state,event.state,arg); 52 } 53 if(leaveValue == false){ 54 console.log('not transform') 55 return false; 56 } 57 58 if(leaveValue == this.SYNC){ 59 return false; 60 } 61 62 this._transition(arg); 63 } 64 65 })(i); 66 } 67 } 68 _StateMachine.prototype._getEvent = function(hash){ 69 var event={}; 70 for (var i = hash.length - 1; i >= 0; i--) { 71 var formType = Object.prototype.toString.call(hash[i].from); 72 if(formType == '[object Array]'){ 73 if(hash[i].from.indexOf(this.state)>=0){ 74 event = hash[i]; 75 break; 76 } 77 }else{ 78 if(hash[i].from == this.state){ 79 event = hash[i]; 80 break; 81 } 82 } 83 } 84 return event; 85 } 86 _StateMachine.prototype._transition = function (args) { 87 var event = this.cacheEvent; 88 var fn = event.name; 89 this.prevState = this.state; 90 this.state = event.to; 91 this.callbacks['on'+event.name]&&this.callbacks['on'+event.name](this,this.prevState,this.state,args); 92 this.callbacks['on'+this.state]&&this.callbacks['on'+this.state](this,this.prevState,this.state,args); 93 this.callbacks['onenter'+this.state]&&this.callbacks['onenter'+this.state](this,event.name,this.prevState,event.state,args); 94 this.callbacks['onafter'+fn]&&this.callbacks['onafter'+fn](this,event.name,this.prevState,event.state,args); 95 console.log('sm state transform '+ this.prevState+ ' to '+ this.state); 96 97 } 98 _StateMachine.prototype.is = function (state) { 99 return this.state == state; 100 } 101 _StateMachine.prototype.can = function (eventName) { 102 var event = this._getEvent(this._hashEvent[eventName]); 103 return JSON.stringify(event) != '{}'; 104 } 105 _StateMachine.prototype.cannot = function (eventName) { 106 var event = this._getEvent(this._hashEvent[eventName]); 107 return JSON.stringify(event) == '{}'; 108 } 109 //以数组的形式返回实例当前状态下能够被触发的行为列表 110 _StateMachine.prototype.transitions = function () { 111 var events = []; 112 for (var e in this._hashEvent) { 113 if(this.can(e)){ 114 events.push(e); 115 } 116 } 117 return events; 118 } 119 return { 120 create:function (config) { 121 return new _StateMachine(config) 122 } 123 } 124 })();
调用
1 var fsm2 = sm.create({ 2 initial: 'hungry', 3 events: [ 4 { name: 'eat', from: 'hungry', to: 'satisfied' }, 5 { name: 'eat', from: 'satisfied',to: 'full' }, 6 { name: 'eat', from: 'full',to: 'sick' }, 7 { name: 'rest', from: ['hungry', 'satisfied', 'full', 'sick'], to: 'hungry' }, 8 ] 9 }); 10 11 12 var fsm = sm.create({ 13 initial: 'green', 14 events: [ 15 { name: 'warn', from: 'green', to: 'yellow' }, 16 { name: 'panic', from: 'yellow', to: 'red' }, 17 { name: 'calm', from: 'red', to: 'yellow' }, 18 { name: 'clear', from: 'yellow', to: 'green' } 19 ], 20 callbacks: { 21 onpanic: function(event, from, to, msg) { alert('panic! ' + msg); }, 22 onwarn: function(event, from, to, msg) { alert('warn! ' + msg); }, 23 onclear: function(event, from, to, msg) { alert('thanks to ' + msg); }, 24 ongreen: function(event, from, to) { document.body.className = 'green'; }, 25 onyellow: function(event, from, to) { document.body.className = 'yellow'; }, 26 onred: function(event, from, to) { document.body.className = 'red'; }, 27 } 28 });