轻松掌握JavaScript状态模式


Posted in Javascript onSeptember 07, 2016

状态模式 

状态模式(State)允许一个对象在其内部状态改变的时候改变它的行为,对象看起来似乎修改了它的类。 

状态模式的使用场景也特别明确,有如下两点:
 1.一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。(有些对象通常会有好几个状态,在每个状态都只可以做当前状态才可以做的事情,而不能做其它状态能做的事儿)

 2.一个操作中含有大量的分支语句,而且这些分支语句依赖于该对象的状态。状态通常为一个或多个枚举常量的表示。 

一、有限状态机

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

通用做法:将状态封装成独立的类(状态机),并将请求委托给当前的状态对象,当对象的内部状态发生改变时,会带来不同的行为变化。 

二、性能优化点

 1.如何管理状态对象的创建和销毁?第一种仅当state对象被需要时才创建并随后销毁(state对象比较庞大,优先选择), 另一种是一开始就创建好所有的状态对象,并且始终不销毁它们(状态改变频繁)。
 2.利用享元模式共享一个state对象。 

举个稍微复杂的例子,相信大家都玩过角色扮演类游戏,里面的角色就有很多种状态(站、走、跑、跳、蹲等),各个状态之间的切换是被规定好了的,且任何时刻都只能处于一种状态中,而在每个状态下,角色只能做当前状态下被允许的行为(如:普通攻击、各种技能攻击、防御等) 

这是我写的移动小球的例子:

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <title></title>
 <script>
  window.onload = function() {
   var FSM = {
    show1: {
     clickBtn: function (key) {
      change.call(this,key);
     }
    },
    show2: {
     clickBtn: function (key) {
      change.call(this,key);
     }
    },
    show3: {
     clickBtn: function (key) {
      change.call(this,key);
     }
    },
    show4: {
     clickBtn: function (key) {
      change.call(this,key);
     }
    }
   };
   var Ball = function () {
    this.curentState = FSM.show1;
    this.div = null;
   };
   Ball.prototype.init = function () {
    var self = this;
    this.div = document.getElementById('go');
    document.body.onkeydown = function (event) {
     var key = event.keyCode;
     self.curentState.clickBtn.call(self,key);
    }
   };
   function change(key){
    var styles = window.getComputedStyle(this.div),
     parentStyles = window.getComputedStyle(this.div.parentNode),
     top = parseInt(styles.top),
     left = parseInt(styles.left);
    if(key === 40){
     top += (top+parseInt(styles.height))<parseInt(parentStyles.height) ? 10 : 0;
     this.div.style.top = top+'px';
     this.curentState = FSM.show3;
    }
    if(key === 38){
     top -= (top > 0 ? 10 : 0);
     this.div.style.top = top+'px';
     this.curentState = FSM.show4;
    }
    if(key === 37){
     left -= (left > 0 ? 10 : 0);
     this.div.style.left = left+'px';
     this.curentState = FSM.show1;
    }
    if(key === 39){
     this.curentState = FSM.show2;
     left += (left+parseInt(styles.width))<parseInt(parentStyles.width) ? 10 : 0;
     this.div.style.left = left+'px';
    }
   }
   var a = new Ball();
   a.init();
  }
 </script>
 <style>
  #div{
   position: absolute;
   width: 80%;
   height: 80%;
   top: 0;
   bottom: 0;
   left: 0;
   right: 0;
   margin: auto;
   border: 1px solid darkcyan;
  }
  #go{
   position:absolute;
   width:50px;
   height:50px;
   left: 10px;
   top:20px;
   border:1px solid gray;
   -webkit-border-radius : 50px;
   -moz-border-radius: 50px;
   border-radius: 50px;
   background-image: radial-gradient(circle, white 5%, black 100%);
  }
 </style>
</head>
<body>
<div id="div">按下方向键移动方块
 <div id="go"></div>
</div>
</body>
</html>

三、JavaScript版本的状态机(以简单的开关灯为例)

 1.通过Function.prototype.call方法直接把请求委托给某个字面量对象来执行

// 状态机
var FSM = {
 off: {
 buttonWasPressed: function() {
  console.log("关灯");
  this.button.innerHTML = "下一次按我是开灯"; // 这是Light上的属性!!!
  this.currState = FSM.on;   // 这是Light上的属性!!!
 }
 },
 on: {
 buttonWasPressed: function() {
  console.log("开灯");
  this.button.innerHTML = "下一次按我是关灯";
  this.currState = FSM.off;
 }
 },
};
 
var Light = function() {
 this.currState = FSM.off; // 设置当前状态
 this.button = null;
};
 
Light.prototype.init = function() {
 var button = document.createElement("button");
 self = this;
 
 button.innerHTML = "已关灯";
 this.button = document.body.appendChild(button);
 this.button.onclick = function() {
 // 请求委托给FSM状态机
 self.currState.buttonWasPressed.call(self);
 }
 
}
 
var light = new Light();
light.init();

2.利用delegate函数

var delegate = function(client, delegation) {
 return {
 buttonWasPressed: function() {
  return delegation.buttonWasPressed.apply(client, arguments);
 }
 };
};
 
// 状态机
var FSM = {
 off: {
 buttonWasPressed: function() {
  console.log("关灯");
  this.button.innerHTML = "下一次按我是开灯";
  this.currState = this.onState;
 }
 },
 on: {
 buttonWasPressed: function() {
  console.log("开灯");
  this.button.innerHTML = "下一次按我是关灯";
  this.currState = this.offState;
 }
 },
};
 
var Light = function() {
 this.offState = delegate(this, FSM.off);
 this.onState = delegate(this, FSM.on);
 this.currState = this.offState; // 设置当前状态
 this.button = null;
};
 
Light.prototype.init = function() {
 var button = document.createElement("button");
 self = this;
 
 button.innerHTML = "已关灯";
 this.button = document.body.appendChild(button);
 this.button.onclick = function() {
 // 请求委托给FSM状态机
 self.currState.buttonWasPressed();
 }
}
 
var light = new Light();
light.init();

状态模式和策略模式很像,它们都封装了一系列的算法或行为,它们都有一个上下文对象来把请求委托给封装类(策略类、状态机),但它们的意图不同:
 1.策略类的各个属性之间是平等平行的,它们之间没有任何联系
 2.状态机中的各个状态之间存在相互切换,且是被规定好了的。

参考文献: 《JavaScript模式》 《JavaScript设计模式与开发实践》

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
MooTools 1.2介绍
Sep 14 Javascript
Mootools 1.2教程 滑动效果(Slide)
Sep 15 Javascript
基于jQuery的Spin Button自定义文本框数值自增或自减
Jul 17 Javascript
jquery.ajax的url中传递中文乱码问题的解决方法
Feb 07 Javascript
JavaScript编写简单的计算器
Nov 25 Javascript
jQuery实现HTML表格单元格的合并功能
Apr 06 Javascript
Bootstrap栅格系统使用方法及页面调整变形的解决方法
Mar 10 Javascript
详细分析JS函数去抖和节流
Dec 05 Javascript
vue2.0 和 animate.css的结合使用
Dec 12 Javascript
jQuery+ajax读取json数据并按照价格排序示例
Mar 28 jQuery
如何给element添加一个抽屉组件的方法步骤
Jul 14 Javascript
jquery实现直播弹幕效果
Nov 28 jQuery
JS简单实现tab切换效果的多窗口显示功能
Sep 07 #Javascript
JS实现的幻灯片切换显示效果
Sep 07 #Javascript
javascript宿主对象之window.navigator详解
Sep 07 #Javascript
Angular 理解module和injector,即依赖注入
Sep 07 #Javascript
JS继承之借用构造函数继承和组合继承
Sep 07 #Javascript
Node.js读写文件之批量替换图片的实现方法
Sep 07 #Javascript
jQuery实现底部浮动窗口效果
Sep 07 #Javascript
You might like
php出现Cannot modify header information问题的解决方法大全
2008/04/09 PHP
使用PHP备份MYSQL数据的多种方法
2014/01/15 PHP
PHP+jQuery 注册模块的改进(三):更新到Smarty3.1
2014/10/14 PHP
十幅图告诉你什么是PHP引用
2015/02/22 PHP
php+ajax实时刷新简单实例
2015/02/25 PHP
php把字符串指定字符分割成数组的方法
2018/03/12 PHP
用javascript实现自定义标签
2007/05/08 Javascript
浅析JS刷新框架中的其他页面 &amp;&amp; JS刷新窗口方法汇总
2013/07/08 Javascript
jQuery前端框架easyui使用Dialog时bug处理
2014/12/05 Javascript
JavaScript精炼之构造函数 Constructor及Constructor属性详解
2015/11/05 Javascript
详解Matlab中 sort 函数用法
2016/03/20 Javascript
jQuery和JavaScript节点插入元素的方法对比
2016/11/18 Javascript
JS检测window.open打开的窗口是否关闭
2017/06/25 Javascript
vue上传图片到oss的方法示例(图片带有删除功能)
2018/09/27 Javascript
微信小程序上传文件到阿里OSS教程
2019/05/20 Javascript
javascript实现图片轮播代码
2019/07/09 Javascript
基于mpvue的简单弹窗组件mptoast使用详解
2019/08/02 Javascript
使用nodejs实现JSON文件自动转Excel的工具(推荐)
2020/06/24 NodeJs
Python ftp上传文件
2016/02/13 Python
谈谈python中GUI的选择
2018/03/01 Python
Python 类属性与实例属性,类对象与实例对象用法分析
2019/09/20 Python
matplotlib 使用 plt.savefig() 输出图片去除旁边的空白区域
2021/01/05 Python
纯CSS3实现鼠标滑过按钮动画第二节
2020/07/16 HTML / CSS
下面关于"联合"的题目的输出是什么
2013/08/06 面试题
如果有两个类A,B,怎么样才能使A在发生一个事件的时候通知B
2016/03/12 面试题
公司财务总监岗位职责
2013/12/14 职场文书
毕业生自荐书
2013/12/18 职场文书
大学生旷课检讨书
2014/01/22 职场文书
小学教师培训感言
2014/02/11 职场文书
乡镇食品安全责任书
2014/07/28 职场文书
大学生作弊检讨书
2014/09/11 职场文书
2014年精神文明工作总结
2014/12/23 职场文书
个人总结怎么写
2015/02/26 职场文书
钱学森观后感
2015/06/04 职场文书
MySQL 使用索引扫描进行排序
2021/06/20 MySQL
Java常用工具类汇总 附示例代码
2021/06/26 Java/Android