状态机——之前我们称之为“有限状态机”(Finite State Machines,FSM)使用状态机可以轻松地管理很多控制器,根据需要显示和隐藏视图。那么,到底什么是状态机?本质上讲状态机由两部分组成:状态和转换器。它只有一个活动状态,但也包含很多非活动状态(passive state)。当活动状态之间相互切换时就会调用状态转换器。状态机如何工作呢?考虑这样一个场景,应用中存在一些视图,它们的显示是相互独立的,比如一个视图用来显示联系人,另一个视图用来编辑联系人。这两个视图一定是互斥的关系,其中一个显示时另一个一定是隐藏的。这个场景就非常适合引入状态机,因为它能确保每个时刻只有一种视图是激活的。的确,如果我们想添加一些新视图,比如一个承载设置操作的视图,用状态机来处理这种场景绰绰有余。我们来对实际的例子做进一步修改,来看一看实现一个状态机的思路是怎样的。这个例子非常简单,没有实现多个转换器类型,但足以满足我们的需要。首先,我们使用jQuery 的事件API 创建一个Events 对象,给它添加绑定和触发状态机的
事件的能力:
var Events = {
bind: function(){
if ( !this.o ) this.o = $({});
this.o.bind.apply(this.o, arguments);
},
trigger: function(){
if ( !this.o ) this.o = $({});
this.o.trigger.apply(this.o, arguments);
}
};
这里的Events 对象本质上是扩展了jQuery 现有的DOM 外部事件的支持,这样我们就可以将它应用到我们的库中。现在我们来创建StateMachine 类,它包含一个主要的函数add() :
var StateMachine = function(){};
StateMachine.fn = StateMachine.prototype;
// 添加事件绑定或触发行为
$.extend(StateMachine.fn, Events);
StateMachine.fn.add = function(controller){
this.bind("change", function(e, current){ //this指向StateMachine的实例,给其绑定change事件
if (controller == current)
controller.activate();
else
controller.deactivate();
});
controller.active = $.proxy(function(){ //这个controller.active为传进来的参数中的active对象,触发提供了设置。例如con1中的active对象 绑定到了sateMachine的原型上,实例化即被触发。
this.trigger("change", controller);
}, this);
};
这个状态机的add() 函数将传入的控制器添加至状态列表,并创建一个active() 函数。当调用active() 的时候,控制器的状态就转换为激活状态。对于激活状态的控制器,状态机将基于它调用activate(),对于其他的控制器,状态机则会调用deactivate()。这里给出两个例子,通过例子可以看出这两类控制器是如何工作的,我们首先将控制器添加至状态机中,然后激活其中一个控制器:
var con1 = {
activate: function(){ /* ... */ },
deactivate: function(){ /* ... */ }
};
var con2 = {
activate: function(){ /* ... */ },
deactivate: function(){ /* ... */ }
};
// 创建一个新的状态机,并添加状态
var sm = new StateMachine;
sm.add(con1);
sm.add(con2);
// 激活第1 个状态
con1.active();
状态机的add() 函数给change 事件创建了一个回调,根据需要调用activate() 或deactivate() 函数。尽管状态机给我们提供了active() 函数,我们同样可以通过手动触发change 事件来改变状态:
sm.trigger("change", con2);
在控制器activate() 函数的内部,我们可以创建并显示它的视图,添加并显示元素。与此类似,在deactivate() 函数内部我们则将元素销毁来隐藏视图。可以通过CSS 的类来隐藏和显示视图,这种方法非常不错,即给元素添加名为.active 的类来显示视图,将它移除就可以隐藏视图:
var con1 = {
activate: function(){
$("#con1").addClass("active");
},
deactivate: function(){
$("#con1").removeClass("active");
}
};
var con2 = {
activate: function(){
$("#con2").addClass("active");
},
deactivate: function(){
$("#con2").removeClass("active");
}
};
然后在样式表中保证视图包含.active 类,否则不包含.active 类:
```javascript
#con1, #con2 { display: none; }
#con1.active, #con2.active { display: block; }
完整代码
var Events = {
bind: function(){
if ( !this.o ) this.o = $({});
this.o.bind.apply(this.o, arguments);
},
trigger: function(){
if ( !this.o ) this.o = $({});
this.o.trigger.apply(this.o, arguments);
}
};
var StateMachine = function(){};
StateMachine.fn = StateMachine.prototype;
$.extend(StateMachine.fn, Events);
StateMachine.fn.add = function(controller){
this.bind("change", function(e, current){
if (controller == current)
controller.activate();
else
controller.deactivate();
});
controller.active = $.proxy(function(){
this.trigger("change", controller);
}, this);
};
var con1 = {
activate: function(){
console.log("controller 1 activated");
},
deactivate: function(){
console.log("controller 1 deactivated");
}
};
var con2 = {
activate: function(){
console.log("controller 2 activated");
},
deactivate: function(){
console.log("controller 2 deactivated");
}
};
var sm = new StateMachine;
sm.add(con1);
sm.add(con2);
con1.active();