轻松掌握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 相关文章推荐
正则表达式语法
Oct 09 Javascript
Add Formatted Data to a Spreadsheet
Jun 12 Javascript
Google 静态地图API实现代码
Nov 19 Javascript
同时使用n个window onload加载实例介绍
Apr 25 Javascript
js中如何复制一个对象并获取其所有属性和属性对应的值
Oct 24 Javascript
Javascript 拖拽雏形(逐行分析代码,让你轻松了拖拽的原理)
Jan 23 Javascript
jquery实现右侧栏菜单选择操作
Mar 04 Javascript
vue2.0 子组件改变props值,并向父组件传值的方法
Mar 01 Javascript
Vue 路由切换时页面内容没有重新加载的解决方法
Sep 01 Javascript
详解Vue.directive 自定义指令
Mar 27 Javascript
详解vue中axios请求的封装
Apr 08 Javascript
微信小程序云开发之新手环境配置
May 16 Javascript
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日期处理函数 整型日期格式
2011/01/12 PHP
教你如何解密 “ PHP 神盾解密工具 ”
2014/06/20 PHP
php操作access数据库的方法详解
2017/02/22 PHP
php在windows环境下获得cpu内存实时使用率(推荐)
2018/02/08 PHP
PHP函数按引用传递参数及函数可选参数用法示例
2018/06/04 PHP
PHP快速排序算法实现的原理及代码详解
2019/04/03 PHP
用js计算页面执行时间的函数
2006/12/07 Javascript
java script编程起步(第三课)
2007/01/10 Javascript
javascript 匿名函数的理解(透彻版)
2010/01/28 Javascript
js png图片(有含有透明)在IE6中为什么不透明了
2010/02/07 Javascript
JS 按钮点击触发(兼容IE、火狐)
2013/08/07 Javascript
通过JQuery将DIV的滚动条滚动到指定的位置方便自动定位
2014/05/05 Javascript
jQuery截取指定长度字符串的实现原理及代码
2014/07/01 Javascript
javascript实现日期格式转换
2014/12/16 Javascript
javascript面向对象之定义成员方法实例分析
2015/01/13 Javascript
jQuery基于ajax实现带动画效果无刷新柱状图投票代码
2015/08/10 Javascript
全面解析标签页的切换方式
2016/08/21 Javascript
基于Bootstrap的Metronic框架实现条码和二维码的生成及打印处理操作
2016/08/29 Javascript
用p5.js制作烟花特效的示例代码
2018/03/21 Javascript
vue中设置height:100%无效的问题及解决方法
2018/07/27 Javascript
微信小程序自定义组件的实现方法及自定义组件与页面间的数据传递问题
2018/10/09 Javascript
微信小程序swiper使用网络图片不显示问题解决
2019/12/13 Javascript
[00:33]2018DOTA2亚洲邀请赛TNC出场
2018/04/04 DOTA
详解在Python和IPython中使用Docker
2015/04/28 Python
selenium+python实现1688网站验证码图片的截取功能
2018/08/14 Python
对python创建及引用动态变量名的示例讲解
2018/11/10 Python
深入浅析python 协程与go协程的区别
2019/05/09 Python
在Python3.74+PyCharm2020.1 x64中安装使用Kivy的详细教程
2020/08/07 Python
Tirendo比利时:在线购买轮胎
2018/10/22 全球购物
Puccini乌克兰:购买行李箱、女士手袋网上商店
2020/08/06 全球购物
法院授权委托书格式
2014/09/28 职场文书
政府四风问题整改措施
2014/10/04 职场文书
个人政风行风自查自纠报告
2014/10/21 职场文书
2014年双拥工作总结
2014/11/21 职场文书
2014年电话销售工作总结
2014/12/01 职场文书
《孙子兵法》:欲成大事者,需读懂这些致胜策略
2019/08/23 职场文书