轻松掌握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 相关文章推荐
web 页面分页打印的实现
Jun 22 Javascript
ymPrompt的doHandler方法来实现获取子窗口返回值的方法
Jun 25 Javascript
jQuery LigerUI 插件介绍及使用之ligerDrag和ligerResizable示例代码打包
Apr 06 Javascript
js 三级关联菜单效果实例
Aug 13 Javascript
2014年最火的Node.JS后端框架推荐
Oct 27 Javascript
js自调用匿名函数的三种写法(推荐)
Aug 19 Javascript
把多个JavaScript函数绑定到onload事件处理函数上的方法
Sep 04 Javascript
jQuery 如何实现一个滑动按钮开关
Dec 01 Javascript
node全局变量__dirname与__filename的区别
Jan 14 Javascript
小程序获取当前位置加搜索附近热门小区及商区的方法
Apr 08 Javascript
原生js实现html手机端城市列表索引选择城市
Jun 24 Javascript
js实现省级联动(数据结构优化)
Jul 17 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 图片上添加透明度渐变的效果
2009/06/29 PHP
PHP中的strtr函数使用介绍(str_replace)
2011/10/20 PHP
php 在线导入mysql大数据程序
2015/06/11 PHP
thinkPHP和onethink微信支付插件分享
2019/08/11 PHP
Laravel框架控制器的request与response用法示例
2019/09/30 PHP
javascript之AJAX框架使用说明
2010/04/24 Javascript
理解JAVASCRIPT中hasOwnProperty()的作用
2013/06/05 Javascript
Bootstrap每天必学之标签与徽章
2015/11/27 Javascript
js和C# 时间日期格式转换的简单实例
2016/05/28 Javascript
浅谈JS中的三种字符串连接方式及其性能比较
2016/09/02 Javascript
ES6生成器用法实例分析
2017/04/10 Javascript
详解vue跨组件通信的几种方法
2017/06/15 Javascript
微信小程序 蓝牙的实现实例代码
2017/06/27 Javascript
浅谈gulp创建完整的项目流程
2017/12/20 Javascript
基于iview的router常用控制方式
2019/05/30 Javascript
js设计模式之代理模式及订阅发布模式实例详解
2019/08/15 Javascript
Layui 动态禁止select下拉的例子
2019/09/03 Javascript
Vue 实现简易多行滚动&quot;弹幕&quot;效果
2020/01/02 Javascript
Python文本统计功能之西游记用字统计操作示例
2018/05/07 Python
Python通过递归获取目录下指定文件代码实例
2019/11/07 Python
TensorFlow tf.nn.max_pool实现池化操作方式
2020/01/04 Python
使用keras2.0 将Merge层改为函数式
2020/05/23 Python
Django中Aggregation聚合的基本使用方法
2020/07/09 Python
Python3爬虫中识别图形验证码的实例讲解
2020/07/30 Python
pycharm 代码自动补全的实现方法(图文)
2020/09/18 Python
HTML5 标准将把互联网视频扔回到黑暗时代
2010/02/10 HTML / CSS
Canvas获取视频第一帧缩略图的实现
2020/11/11 HTML / CSS
Born鞋子官网:Born Shoes
2017/04/06 全球购物
摄影实习自我鉴定
2013/09/20 职场文书
单位未婚证明范本
2014/01/18 职场文书
班训口号大全
2014/06/18 职场文书
关于调整工作时间的通知
2015/04/24 职场文书
2015入党自传书范文
2015/06/26 职场文书
认识实习感想
2015/08/10 职场文书
《草虫的村落》教学反思
2016/02/20 职场文书
导游词之岳阳楼
2019/09/25 职场文书