代码质量 与 设计模式应用
一、代码指标
1、健壮性
什么意思呢? 百度:在异常和危险情况下系统生存的能力。 用人的健壮性做个比方:
如果一个人很健壮,那一般的小病小灾打不倒他,如感冒、咳嗽 对比过来,一个程序很健壮,那在出现预期之外的错误(eg:文件不存在),减少找bug的难度和影响程度。
健壮性好的代码有哪些特点
减少找bug的难度 很多同学可能有这么一个问题:发现自己每天写代码 2小时,改bug 6小时, 一天8个小时就这么过去了 降低对程序的影响程度 如果程序出现了问题,不至于这一个问题让我们整个系统跑不起来了
保证健壮性的手段
健壮性不同其他性能,没有多抽象的概念,也没有多深的思维,主要就是个人的习惯
1、参数层面
- 基础类型判断
先来看下面一个例子:
这就是典型的参数类型引起的问题 [如何解决?]function add(a, b){ return a + b; } // 正常入参 没什么问题 add(1,2); // 少传一个参数时 add(1) // ==> NaN 这里是不会报错的哦! // 这时如果这个返回值是通过 axios.get()传到后台会是什么情况? axios.get('url',{ num: add(1) }) // ==> 400 bad request // 试想:如果你的代码量比较大时,要定位到这个问题是不是要花很长时间,还要debug之类的function add(a, b){ // 1、基础参数类型判断 if(typeof a === 'number' && typeof b === 'number'){ return a + b; } else { throw new Error('a or b is not a number') } }
- 参数是配置对象 选项合并
[看Vue是怎么做的?]// 以Vue为例: new Vue({}); // 有哪些必传参数? // 如何保证这些必传参数都传了?// 必传参数: el template // 如何保证必传值不为空: 提供一个默认配置,把new时的入参与默认配置做合并 function Vue(config){ let _default = { el: document, template:'<div></div>' } for(let key in config){ _default[key] = config[key] || _default[key]; } }
- 参数是某个类的实例
这种情况,一般是我们在造轮子的时候会用到
[看这种情况怎么保证健壮性]// Class1 是一个类 function Class1() { } // 这里有个方法fn 参数要求是Class1的实例 function fn(obj){ }// Class1 是一个类 function Class1(){ } // 这里有个方法fn 参数要求是Class1的实例 function fn(obj){ if(obj instanceof Class1){ // dosomthing } else { throw new Error('obj 必须是 Class1的实例') } }
这种情况在实际开发中可能用到的比较少, 但在自己造轮子的时候,这种情况就会非常多
-
js中特有的一个问题
// function 除了可能是一个方法,还可能代表一个什么东东? function a(){}[如何保证?]
function vue(){ if(this instanceof vue){ // 通过instanceof vue 判断this 是不是vue实例 // 类实例 } else { // 当作一个方法调用 // return new vue(); // 这样即使当一个方法调用 ,拿到的还是一个vue的实例 // 像一些框架之类的都会有这种措施 vue是这样 throw new Error('vue is a constructor and should be called with the `new` keyword ') } } vue(); // 直接调用时,this ---> window new vue(); // new 操作符调用时, this 指向vue实例 通过instanceof vue 可以判断
易错代码
代码错了就是错了,什么叫易错代码?
-
不由自己控制的出错代码
// 例如: 前端请求某个接口,接口返回的数据结构 // 预期结构是下面这样的: let res = { list:[ { attrA: 1 } ] } // 是这样取值的 let attrA = res.list[0].attrA; // 如果 list 为空或[] 时,我们取值就会报错 Cannot read property 'attrA' of undefined[怎么解决呢?] 一般有两种方法:
- 模拟一个这样的数据结构
let item = (res.list || [])[0]; let attrA = (item || {}).attrA;- 用&&操作符 先判断为空的情况
let item = res.list && res.list[0] // 使用与操作符 判断为空的情况 let attrA = item && item.attrA;
前端易错性代码出现可能会少些,后端就非常多
// 在下载某个文件时, // 1、文件存在,正常下载 // 2、文件不存在,就会出错了 // 如果不处理出错,后端服务可能就停了,那这样影响就非常严重了, // 所以后端经常会看到这样的代码段 try{ // 易错代码 } catch (e){ // 记录日志 } // 先把出错的信息记录下来,保证程序不会挂掉, // 再通过日志分析出错原因
变量权限 这个就有点抽象了
变量应该不应该被篡改,能不能够被篡改 变量应不应该被读取,能不能够被读取,在哪里能被读取 这就是变量的权限
变量权限跟健壮性有什么关系呢?
// 还以Vue为例:
// 在使用vue-router的时候,有没有留意这样一个问题:
this.$router = {}; // 给$router赋值,但 this.$router的值不会变
[vue-router是怎么做的呢?]
// vue-router install 方法
function install(Vue){
let _router = {}; // 局部变量
// 屏蔽了set方法,使你给$router赋值时无效
Object.defineProperty(Vue.prototype, '$router', {
get: function get () {
// return this._routerRoot._router // 源码是这样的
return _router;
}
});
}
2、可读性
看到变量或方法就知道这个变量,这段代码是做什么用的
语义化
定义有意义的名字: 描述某个变量或方法在业务中的作用
// 这些都是没有意义, 大量的这种命名,在后期阅读代码时,简直是个灾难
function a(){
}
function b(){
}
如何命名呢? 可以按这个思路来: 在业务中的作用 --> 用中文描述它的作用 --> 翻译成英文 尽量做到见名知义
命名规范
这个不多说了,
比如:js可以这样命名:
1、类命名:首字母大写
2、普通方法变量,小驼峰命名
3、常量: 全大写,
4、局部变量:_开头
结构清晰
-
if-else 问题
if(true){ if(true){ } else if(true){ } else { } } else if(true){ if(true){ } else if(true){ } else { } } else { if(true){ } else if(true){ } else { } } -
回调函数问题
需求:一个操作需要调三个接口A,B,C,B依赖A的返回结果,C依赖B的返回结果
// 用jquery 实现就是这种样式 $.ajax({ url:'urlA', params:{}, success: function(resA){ $.ajax({ url:'urlB', params: resA, success: function(resB){ $.ajax({ url:'urlC', params: resB, success: function(resC){ console.log(resC) } }) } }) } })ES6 之后引用了 promise async 主要就是为了解决回调问题 避免回调地狱
// promise then 方式解决 function request(url, params){ return new Promise((resolve,reject) => { $.ajax({ url:url, params: params, success:function(res){ resolve(res); }, error: function(err){ reject(err) } }) }) } // 调用 request('urlA', {}) .then(res => request('urlB', res)) .then(res => request('urlC', res)) .catch(err => console.log(err)); // async async function getData(url, params){ return axios.post(url, params); } //调用 let resA = await getData('urlA', {}); let resB = await getData('urlB', resA); let resC = await getData('urlC', resB);
3、可复用性
DRY原则 Don't Repeat Yourself
写过一遍的操作,就不重复第二遍了 尽量不去写重复代码, 这可能带来一些维护上的负作用,这个就要把握一个度了 要看某段代码有没有提取的必要,比如重复了多少次,使用量大不大
逻辑复用,提取代码
针对局部某个操作,某个功能而言 重复的部分提取成一个公用的方法
创建公用模板
针对全局性的,创建公用模块: layout | header | footer | common.css | util.js
4、可扩展性
程序上的一个终极难题 对开发人员的架构思维,模块思维要求是非常高的
产品经理不可能不修改他的需求,就像程序员不可能不写bug一样 如果在写代码的时候预先考虑到后期需求可能的变化,那在需求变化时,你就会非常舒服了
-
模块分明
积木式编程,随时可以插入、移除某个模块
-
耦合度低
划分低耦合的模块,并高效设计模块间的沟通 (架构层面讲) 比如你要开个饭店,你需要怎么设置你的组织架构?
厨师模块,服务员模块,收银员模块(老板)
-
合适的扩展技巧
应用设计模式
二、设计模式概论
1、创建型设计模式
帮助我们优雅的创建对象
-
工厂模式
大量创建对象
Jquery时代,我们需要大量频繁的操作dom
// $就是一个工厂,他批量生产jquery对象, 根据你传入的选择器,生成一批jquery对象 $('.className')[如何实现一个工厂模式呢?] 球类工厂
需求:我们要生产不同类型的球:basketball footerball tennis(网球)...
// 球类工厂 function ballFactory(type){ switch(type){ case 'football': return new football(); break; case 'basketball': return new basketball(); break; case 'tennis': return new tennis(); break; default: break; } // 可以在局部 function football(){} function basketball(){} function tennis(){} } // 可以在原型链上 ball.prototype.football = function(){} ball.prototype.basketball = function(){} ball.prototype.tennis = function(){}[再看看jquery是如何实现的] Jquery如何实现工厂模式的
// 静态函数 jquery的实现 function jquery(params){ return new jquery.fn.init(params); } jquery.fn = {}; jquery.fn.init = function (params) {}; window.$ = jquery; $('.className');// 实际是通过new jquery静态属性fn上的init方法jquery是通过挂载一个静态属性实现的
-
建造者模式
精细化组合一个对象:类似建房子 盖房子明, 先把砖,门,窗这些材料都准备好,再把这些材料搭在一起,就建成了.
关键思想 就是先把相关的模块提前独立开发好,再把各个模块拼装集成到一起
明显的标识就是 传入了一大堆的配置信息
//建造者模式: 一般实现方案 // 关键思想 先把相关的模块放一边开发好,再把各模块集成到一起 function F1(){ // 直接绑定 this.model1 = new Model1({}); this.model2 = new Model2({}); this.model3 = new Model3({}); } function Model1(params){} function Model2(params){} function Model3(params){}vue 也是用建造者模式实现的, 但双是跟一般的方案又不太一样 混入方式
// 明显的标识就是 传入了一大堆的配置信息 // 方法2 Vue 是怎么做的 // 先把各个模块独立开发好,之后再混合进实例里 function Vue (options) { // 健壮性校验 if (!(this instanceof Vue) ) { warn('Vue is a constructor and should be called with the `new` keyword'); } this._init(options); } // 下面就是各个模块的混入Vue原型上 initMixin(Vue); stateMixin(Vue); eventsMixin(Vue); lifecycleMixin(Vue); renderMixin(Vue); -
单例模式
全局只有一个,对象只能被创建一次
如何实现 单例
// 常规类:靠挂在这个类上面的一个静态属性 function Class1(params){ if(Class1.instance){ // 查检有没有实现,有则直接返回该实例,无则创建实例,并增加标识 return Class1.instance; } else { this.name ='xxx' this.attr = 'bbbb'; Class1.instance = this; } } // 两个new 是同一个实例 let class11 = new Class1(); let class12 = new Class1(); console.log(class11 == class12)再看vuex vue-router vuex: 全局只有一个,如果有多个,那页面的状态从哪个取也是问题 vue-router:全局中有一个,如果有多个的话,页面跳转的时候,选用哪个就是一个问题
// 以vue-router为例子 // vue插件时如何保证单例的 以vue-router为例 // 通过一个参数,变量,静态属性去标识这个类是否被new 过,已经new过的,不再去new Vue.use(vueRouter); // Vue.use 实际上是调用了插件的install方法 // vue-router { var _vue; function install(vue){ if(_vue === vue && install.installed) return; _vue = vue; install.installed = true;// 静态属性 } }
问题:
需求1:实现一个消息提示弹框插件,[用什么模式?] 弹框提示插件 分析: 因为一个页面的消息提示可能会很多,添加成功,添加失败,网络出错 会需要频繁创建对象,所以考虑用工厂模式实现
// 工厂模式实现 调用如下
pop('message')
// OR
pop.confirm('message')
// 建造者模式实现,调用如下
pop('message')
// OR
let popObj = new pop('message')
popObj.show();
// 哪个用起来更方便, 虽然看起来只少了一个new
需求2: 实现一个编辑器插件:有前进,后退,改变字体,大小,颜色功能,[用什么模式?] 编辑器插件 需求: 有前进,后退,改变字体,大小,颜色功能
分析: 1、一般一个页面只有一个,不需要频繁创建 2、编辑器可能还要涉及到复杂的配置,需要精细化创建
建造者模式
// html 初始化--> 事件绑定 --> 前进后退模块 -->数据记录模块 --> 字体控制模块 --> 数据渲染模块
// 前进后退: 一般都是数据驱动思维,
[{color:'red', content:'hello'}, {color:'green', content:'hello'}]
2、结构型设计模式
帮助我们更优雅的设计代码结构: 策略,享元
还以一个具体的需求为例:
需求:写一个表单验证工具,给我要验证的input 值 value 变化时,应用对应的规则,自动验证 分析: 1、首先是包含的模块:dom初始化模块 --> 事件绑定模块 --> 验证模块 --> 消息提示模块 2、一个页面上可能有多个需要验证的dom,所以适合工厂模式
// 1、防JQuery实现
// 初始化
function t(dom, msgDom){
return new t.init(dom, msgDom);
}
t.init = function(dom, msgDom){
this.dom = dom;
this.msgDom = msgDom;
this.validateArr = [];
}
// 事件绑定
t.init.prototype.initBind = function(params){
let self = this;
this.dom.onblur = function(){
self.run(this.value)
}
}
// 验证模块
// 变化可能性最大,需要细化
// 考虑验证模块在以后的需求中可能需要扩展,
// 所以可以提前预留好自定义扩展方法,预置一些基础的验证规则,减少代码重复
// 以队列的形式存放验证规则,方便扩展
dt.init.prototype.add = function(fn){
if(typeof fn === 'function'){
this.validateArr.push(fn);
} else if(typeof fn === 'string'){
// 预置的验证规则 下面这样写,可读性差,代码不清楚
if(fn === 'isPhone'){
this.validateArr.push(()=>{/*手机号验证*/})
} else if(fn === 'isNumber'){
this.validateArr.push(()=>{/**是否是数字*/})
} else if(fn === 'isEmail'){
}
}
}
t.init.prototype.run = function(value){
while(this.validateArr.length > 0){
// _result 是约定好的验证结果的数据结构
// {success: true|false, msg:''}
let _result = this.validateArr.shift().run(value);
if(!_result.success){
this.sendMsg(_result.msg);
break; // 一个验证失败就停止验证,减少不必要的循环
}
}
}
// 消息提示模块
t.init.prototype.sendMsg = function(msg){
this.msgDom.innerHtml = msg;
}
// 后期的使用 职责链模式
t('input', 'errorMsg')
.add('isPhone')
.add(() => {/**自定义验证1 */})
.add(() => {/**自定义验证2 */})
[优化后的验证工具] 表单验证工具
需求:写一个表单验证工具,给我要验证的input 值 value 变化时,应用对应的规则,自动验证 分析: 1、首先是包含的模块:dom初始化模块 --> 事件绑定模块 --> 验证模块 --> 消息提示模块 2、一个页面上可能有多个需要验证的dom,所以适合工厂模式
// 1、防JQuery
function t(dom, msgDom){
return new t.init(dom, msgDom);
}
t.init = function(dom, msgDom){
this.dom = dom;
this.msgDom = msgDom;
this.validateArr = [];
}
// 2、思考以后的扩展性,想想模块是否需要细化, 模块越细,扩展越方便
// 验证模块变化最大,细化
// 开启验证模块,验证队列模块
t.init.prototype.initBind = function(params){
let self = this;
this.dom.onblur = function(){
self.run(this.value)
}
}
t.init.prototype.add = function(fn){
if(typeof fn === 'function'){
this.validateArr.push(fn);
} else if(typeof fn === 'string'){
// 预置的验证规则 下面这样写,可读性差,代码不清楚
// 引入设计模式解决
// if(fn === 'isPhone'){
// this.validateArr.push(()=>{/*手机号验证*/})
// } else if(fn === 'isNumber'){
// this.validateArr.push(()=>{/**是否是数字*/})
// }
// 策略模式解决
// 简单if-else可以很好解决
let strage = {
isPhone:function(params){},
isNumber: function(params){},
// 更多验证规则
}
this.validateArr.push(strage[fn]);
// 这里涉及的逻辑比较简单,如果逻辑更复杂一点,可能这一个简单的策略就无法解决
// 比如:由单一的条件变化,变成几个组合条件变化
// 状态模式
// 核心:根据对象不同的状态,让对象展示不同的行为, 相当于加了状态管理的策略模式
}
}
t.init.prototype.run = function(value){
while(this.validateArr.length > 0){
// _result 是约定好的验证结果的数据结构
// {success: true|false, msg:''}
let _result = this.validateArr.shift().run(value);
if(!_result.success){
this.sendMsg(_result.msg);
break;
}
}
}
t.init.prototype.sendMsg = function(msg){
this.msgDom.innerHtml = msg;
}
// 后期的使用 职责链模式
t('input', 'errorMsg')
.add('isPhone')
.add(() => {/**自定义验证1 */})
.add(() => {/**自定义验证2 */})
需求2:一个div 实现 上、下、左、右、左上、左下、....这样移动 moveDiv('left') // 左移 moveDiv('left','top') // 左上移
function moveDiv(){
if(arguments.length === 1){
// 可以用策略模式
if(arguments[0] == 'left'){
moveLeft();
} else if (arguments[0] === 'right'){
moveRight();
}
} else if(arguments.length === 2){
// 这里简单的策略模式就不容易了
if(arguments[0] == 'left' && arguments[1] == 'top'){
moveLeft();
moveTop();
}
}
}
[状态模式实现moveDiv] 移动Div
需求2:一个div 实现 上、下、左、右、左上、左下、....这样移动 moveDiv('left') // 左移 moveDiv('left','top') // 左上移
// 用状态模式实现
// 这时候moveDiv就成了一个类了,不是一个方法了
function moveDiv(params){
this.stateArr = [];//因为存在复合运动的行为,所以需要一个数组去存储数据
}
moveDiv.prototype.run = function(params){
// arguments 类数组 将类数组转成真正数组
// 这里有几种方法把类数组转成数组?
this.stateArr = Array.prototype.slice.call(arguments);
// 策略模式
let strage = {
left: moveLeft,
right:moveRight,
top:moveTop
};
this.stateArr.forEach(state => {
strage[state]();
});
}
let moveObj = new moveDiv();
moveObj.run('left', 'top');
使用设计模式之后,这个模块的代码更简洁更易读了
享元模式
存在类似对象和类似代码块时,用于减少类似代码块 这个享元是提取 相同的内容,还是不同的内容? 享元模式里的享无是 代码中的不同点 把不同的提出来,剩下的就是相同的,这样代码块就由多化一了 与平时提取公共代码不是一个意思
以jquery.extend方法为例 extend实现的功能: $.extend({a:1}) // 会把对象扩展到$对象上$.a = 1; 为jquery对象扩展方法属性使用 $.extend({a:1},{b:2}) // ==> {a:1,b:2}
// 不用设计模式时,是这样实现的
$.extend = function(){
if(arguments.length === 1){
for(let item in arguments[0]){
this[item] = arguments[0][item]
}
} else if(arguments.length === 2){
for(let item in arguments[1]){
arguments[0][item] = arguments[1][item];
}
}
}
[jquery的extend 享元模式实现] jquery.extend 享元模式实现
extend实现的功能: $.extend({a:1}) // 会把对象扩展到$对象上$.a = 1; 为jquery对象扩展方法属性使用 $.extend({a:1},{b:2}) // ==> {a:1,b:2}
// 不用设计模式时,是这样实现的
$.extend = function(){
// 享元模式,提取不同点:
// 1、for in 的对象不同,
// 2、接收的对象不同
let source = arguments[0];
let target = this;
if(arguments.length === 2){
source = arguments[1];
target = arguments[0];
}
for(let item in source){
target[item] = source[item]
}
}
总结应用场景: 两个if else 分支中,两段代码块非常相似时,就可以用享元模式了
3、行为型设计模式
模块间的行为模式的总结,帮助组织模块的沟通:装饰者 观察者
装饰者模式
目的:不重写方法的扩展方法 应用场景:当一个方法需要扩展,但又不方便或不能去修改方法时 是公共的方法 或他人的方法 或原生方法 或第三方模块的方法 简单理解 在不去修改原方法的情况下,扩展方法的功能
需求: 删除按钮--> 点击可以删除 ---> 但没有提示 ---> 好多删除按钮都是这样实现,产品需求是要给出删除提示 分析:在原来的删除功能基础上,扩展出提示功能
1、全部改写 2、找到原来定义,修改原方法,增加提示
[你会选哪种方式实现?] 装饰者模式
目的:不重写方法的扩展方法 应用场景:当一个方法需要扩展,但又不方便或不能去修改方法时 是公共的方法 或他人的方法 或原生方法 或第三方模块的方法 简单理解 在不去修改原方法的情况下,扩展方法的功能
需求: 删除按钮--> 点击可以删除 ---> 但没有提示 ---> 好多删除按钮都是这样实现,产品需求是要给出删除提示 分析:在原来的删除功能基础上,扩展出提示功能
1、全部改写 2、找到原来定义,修改原方法,增加提示
function decorate(dom, fn){
// 健壮性校验
if(typeof dom.onClick === 'function'){
// 装饰者 三步走
// 1、重写原方法,或定义新方法;
// 2、提取老方法,并调用
// 3、加入新方法
let _oldFn = dom.onClick; // 在方法被重写前提取
dom.onClick = function(params){
_oldFn.call(this);
fn.call(this);
}
}
}
// 使用
decorate('btnDel', function(params){
console.log('删除成功')
});
vue2 的双向数据绑定
// Vue的双向绑定 非数组属性
Object.defineProperty(vue,'dataKey',{
get: function(){ },
set: function(){ }
})
// 是对象里的属性变了,会触发, 它没办法用到数组上,那Vue对数组是怎么实现双向绑定的
[vue2 对数组属性实现双向绑定] Vue2 数组的双向绑定
// 数组:利用装饰者模式,给数组的 push pop
let arr = ['push', 'pop', 'shift']
let arrProto = Array.prototype;
let arrCopy = Object.create(arrProto); // 为了不污染旧原型链,提前拷贝一份出来
arr.forEach(method => {
arrCopy[method] = function (){
let _ref = arrProto[method].apply(this, arguments);
dep.notify();// 触发更新
}
})
// 把data里的所有数组,都变成这里的这个arrCopy
// 主要是一些技巧
**观察者模式** > 应用场景: > 1、异步模块沟通 >>a 异步模块;b 同步模块; b需要a处理完成后a的消息 观察者 类似事件监听
2、方便加入新的模块, 本来没有想到某个模块会突然要加入的情况
例如:聊天室的沟通:我和你在聊天室的沟通(聊天室就是观察者),这样别人加入更容易 根本没有考虑过你要加入时,你非要加入,实在没办法了,才会引入观察者模式; 虽然使用观察者容易做到某件事儿,但它也确实要花费一些开销
看这样一个抽奖的需求
需求:抽奖转盘,特点是越转越快, 模块分析: 奖品初始化html --> 最终结果选定 --> 转动控制 --> 转动效果
转动控制模块 调用 转动效果模块, 转动效果只负责转动效果; 转完之后,询问控制模块接下来怎么转; 转动效果模块接收消息体{moveTime: 10, speed: 200} 多长时间内转动几个奖品 --> setInterval 异步了, 转动控制与转动效果的沟通问题在于:转动控制不知道什么时候转动效果结束,因为转动速度是不恒定的
// 先定义好几个模块
// 初始化模块
function htmlInit(){ }
// 抽奖结果模块
function getResult(){ }
// 转动控制模块
function moveControll(){ }
// 动画效果:1,2,3,4,5...10 依次高亮显示
function move(config){
let timer = setInterval(() => {
// 转完一个周期后需要向控制块请求接下来怎么转
// 不用设计模式时,就直接通过callback访问
// 要使用设计模式的话,应该用什么模式?
}, config.speed);
}
[抽奖转盘 观察者模式] 观察者模式
需求:抽奖转盘,特点是越转越快, 模块分析: 奖品初始化html --> 最终结果选定 --> 转动控制 --> 转动效果
转动控制模块 调用 转动效果模块, 转动效果只负责转动效果; 转完之后,询问控制模块接下来怎么转; 转动效果模块接收消息体{moveTime: 10, speed: 200} 多长时间内转动几个奖品 --> setInterval 异步了, 转动控制与转动效果的沟通问题在于:转动控制不知道什么时候转动效果结束,因为转动速度是不恒定的
// 观察者三要素:队列,注册,触发
function observer(params){
// 1、队列
this.message = {
}
}
// 2、注册
observer.prototype.regist = function(type, fn){
this.message[key] = fn;
}
// 3、触发
observer.prototype.fire = function(type){
this.message[key]();
}
var observerObj = new observer();
var _domArr = [];
function htmlInt(params){
for(let i = 1; i <= 5; i++){
_domArr.push(document.body.append(`<div>${i}</div>`));
}
}
function getResult(params){
let _num = Math.random() * 10 + 40; // 40是基础动画圈数
return Math.floor(_num, 0);
}
function moveControll(params){
let result = getResult();
let _circle = Math.floor(result/10, 0);// 基础动画圈
let _runCircle = 0; // 已经转了多少圈
let stopNum = result%10; // 最终停留的奖品数
let _speed = 200; // 转速
observerObj.regist('finish', () => {
let _time = 0; // 应该转几个数
_speed -=50; // 转一圈 速度加快50
_runCircle++; // 已转的圈数
if(_runCircle <= _circle){ // 未达到指定的圈数
_time = 10; // 继续转10个数
} else {
_time = stopNum;
}
move({moveTime: _time, speed: _speed});
});
}
// 动画效果:1,2,3,4,5...10 依次高亮显示
function move(config){
let nowIn = 0;
let rmNum = 9; // 移除效果的索引
let timer = setInterval(() => {
// 单独处理第10个跳第1个的情况
if(nowIn == 0){ // 代码相似了,可以优化了
_domArr[9].setAttribute('class', 'item');
_domArr[nowIn].setAttribute('class', 'item item-on');
} else{
_domArr[nowIn-1].setAttribute('class', 'item');
_domArr[nowIn].setAttribute('class', 'item item-on');
}
// 享元模式
// if(nowIn != 9){
// rmNum = nowIn--
// }
// _domArr[rmNum].setAttribute('class', 'item');
// _domArr[nowIn].setAttribute('class', 'item item-on');
nowIn++;
if(nowIn == config.moveTime){
clearInterval(timer);
observerObj.fire('finish');
}
}, config.speed);
}
创建型设计模式 --> 创建对象阶段使用 结构型设计模式 --> 用在一个对象内部代码优化 行为型设计模式 --> 模块之间的沟通交互
三、设计模式解决问题
- 1、if-else模式
- 2、减少重复代码
- 3、更好的扩展方法
- 4、解耦模块