轻松掌握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 相关文章推荐
javascript实现列表滚动的方法
Jul 30 Javascript
javascript中的正则表达式使用详解
Aug 30 Javascript
js实现文字截断功能
Sep 14 Javascript
JavaScript与java语言有什么不同
Sep 22 Javascript
JavaScript实现使用Canvas绘制图形的基本教程
Oct 27 Javascript
vue-cli的eslint相关用法
Sep 29 Javascript
node使用promise替代回调函数
May 07 Javascript
在小程序Canvas中使用measureText的方法示例
Oct 19 Javascript
详解CommonJS和ES6模块循环加载处理的区别
Dec 26 Javascript
Vue实现拖放排序功能的实例代码
Jul 08 Javascript
vue实现中部导航栏布局功能
Jul 30 Javascript
layui下拉列表select实现可输入查找的方法
Sep 28 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
Linux下CoreSeek及PHP扩展模块的安装
2012/09/23 PHP
PHP重定向与伪静态区别
2017/02/19 PHP
Javascript动态绑定事件的简单实现代码
2010/12/25 Javascript
初识SmartJS - AOP三剑客
2014/06/08 Javascript
JavaScript 闭包详细介绍
2016/09/28 Javascript
JavaScript实现星星等级评价功能
2017/03/22 Javascript
javascript实现日期三级联动下拉框选择菜单
2020/12/03 Javascript
使用vue-aplayer插件时出现的问题的解决
2018/03/02 Javascript
一个Vue视频媒体多段裁剪组件的实现示例
2018/08/09 Javascript
antd组件Upload实现自己上传的实现示例
2018/12/18 Javascript
浅析vue-router中params和query的区别
2019/12/24 Javascript
Vue项目移动端滚动穿透问题的实现
2020/05/19 Javascript
vue在响应头response中获取自定义headers操作
2020/07/24 Javascript
绘制微信小程序验证码功能的实例代码
2021/01/05 Javascript
Python实现的检测网站挂马程序
2014/11/30 Python
Django返回json数据用法示例
2016/09/18 Python
python正则表达式及使用正则表达式的例子
2018/01/22 Python
python ffmpeg任意提取视频帧的方法
2020/02/21 Python
Python 实现打印单词的菱形字符图案
2020/04/12 Python
Python plt 利用subplot 实现在一张画布同时画多张图
2021/02/26 Python
法国二手MacBook销售网站:Okamac
2019/03/18 全球购物
阿联酋优惠券服务:Living Kool
2019/12/12 全球购物
CHRONEXT英国:您的首选奢华腕表目的地
2020/03/30 全球购物
Java的五个基础面试题
2016/02/26 面试题
在校生汽车维修实习自我鉴定
2013/09/19 职场文书
人力资源管理专业自荐信
2014/06/24 职场文书
单位委托函范文
2015/01/29 职场文书
大学生自荐信范文
2015/03/05 职场文书
家长会后的感想
2015/08/11 职场文书
2016参观监狱警示教育活动心得体会
2016/01/15 职场文书
2016关于预防职务犯罪的心得体会
2016/01/21 职场文书
工人先锋号事迹材料(2016精选版)
2016/03/01 职场文书
学会用Python实现滑雪小游戏,再也不用去北海道啦
2021/05/20 Python
上帝为你开了一扇窗之Tkinter常用函数详解
2021/06/02 Python
豆瓣2021评分最高动画剧集-豆瓣评分最高的动画剧集2021
2022/03/18 日漫
nginx日志格式分析和修改
2022/04/28 Servers