我们写项目其实就是写模块然后设计它们之间的沟通,设计模式说白了就是帮助我们更好的设计模块, 更好的组织它们之间的沟通。
帮助我们组织模块
通过一些设计模式,组织模块间的组成结构
帮助我们设计沟通
有的设计模式可以帮助我们设计模块间如何沟通
提高代码质量
通过设计模式,让代码更加优雅
开闭原则
我们的程序要对扩展开放,对修改关闭;我们的程序要给具体使用的时候扩展的接口,但是在具体使用的时候不能让其修改我们的源码, 也就是说我们不用修改源码就能扩展功能,像 vue,react 等都有扩展的接口。
单一职责原则
我们的模块只做一件事情,模块的职责越单一越好。
依赖倒置原则
我们的上层模块不要依赖与具体的下层模块,应该依赖于抽象
假如下面是一套点餐系统,我们在上层和下层之间加一个抽象层;下层如何变动都不会影响到上层,只需更改抽象层即可。
// 具体层
function Food1() {}
function Food2() {}
function Food3() {}
// 抽象层
function resturn(food) {
var list = {
food1: new Food1(),
food2: new Food2(),
food3: new Food3()
}
return list[food]
}
// 上层
function order(food) {
return resturn(food)
}
我们的接口要细化,功能要单一,一个接口不要调用太多方法,使其能力单一,听起来像单一职责原则;但是 2 者的关注点不同, 单一职责原则主要关注于模块本身,接口隔离原则关注于接口;我们尽量细化接口,每个接口做的事情尽量单一化。
我们让 2 个对象之间产生沟通,我们最好让 2 个对象之间知道的越少越好,没必要 2 者之间非常的了解;我们的中介者模式是一个很好体现迪米特法则的设计模式,中介者模式让 2 个对象之间没必要直接的沟通,如果直接沟通需要了解 2 者之间的 api 和彼此的调用方式,这个时候我们可以采用一个中介者来转达我们的需求,而不用彼此知道
它主要关注于继承,它的意义是任何使用父类的地方都可以用子类去替换,直白的说我们子类继承父类的时候,我们的子类必须完全保证继承父类的属性和方法,这样的话父类使用的地方,子类可以进行替换
后面学习到设计模式都是在体现这些设计原则
这些设计模式可以帮助我们优雅地创建对象
帮助我们优雅地设计代码结构
模块之间行为的模式总结,帮助我们组织模块行为
一些帮助我们优化代码的技巧
工厂模式-大量创建对象
单例模式-全局只能有我一个
建造者模式-精细化组合对象
原型模式-javaScript 的灵魂
外观模式-给你的一个套餐
适配器模式-用适配代替更改
装饰者模式-更优雅地扩展需求
享元模式-共享来减少数量
桥接模式-独立出来,然后再对接过去
观察者模式-我作为第三方转发
状态模式-用状态代替判断
策略模式-算法工厂
职责链模式-像生产线一样组织模块
命令模式-用命令去解耦
迭代器模式-告别 for 循环
链模式-链式调用
委托模式-让别人代替你收快递
数据访问模式-一个方便的数据管理器
惰性模式-我要搞机器学习(第一次执行完后把状态记录下来)
等待者模式-等你们都回来再吃饭
创建型设计模式到底是怎么样使用的,利用创建型设计模式更好的封装代码更好的创建对象
封装的目的?
定义的变量不会污染到外部
能够作为一个模块调用
遵循开闭原则
什么是好的封装?
变量外部不可见
调用接口使用
留出扩展接口
工厂模式
目的:方便我们大量创建对象
应用场景:当某一个对象需要经常创建的时候
建造者模式
目的:需要组合出一个全局对象
应用场景:当要创建单个、庞大的组合对象时
单例模式
目的:需要确保全局只有一个对象
应用场景:为了避免重复新建,避免多个对象存在相互干扰
function Factory (type) {
switch (type) {
case 'type1'
return new Type1()
case 'type2'
return new Type2()
case 'type3'
return new Type3()
}
}
工厂模式就是写一个方法,只需调用这个方法,就能拿到你要的对象
// 模块1
function Mode1() {}
// 模块2
function Mode2() {}
// 最终使用的类
function Final() {
this.mode1 = new Model()
this.mode2 = new Mode2()
}
把一个复杂的类各个部分,拆分成独立的类,然后再最终类里组合到一块,final 为最终给出去的类
单例模式的做法不是很固定,我们更重要的记住是保证全局只有一个对象的思想
// 作为单例实例化的对象
let SingLeton = function(name) {
this.name = name
}
/*
在SingLeton挂在一个getInstance方法,只能通过getInstance方法来获取
SingLeton的实力化对象
*/
SingLeton.getInstance = function(name) {
if (this.instance) {
return this.instance
}
return (this.instance = new SingLeton(name))
}
通过定义一个方法,使用时只允许通过此方法拿到存在内部的同一实例化对象
实现一个多彩的弹窗?
需求:弹窗有多种,它们之间存在内容和颜色上的差异
;(function(ROOT) {
// 消息弹性
function infoPop() {}
// 确认弹窗
function confirmPop() {}
// 取消弹窗
function cancelPop() {}
// 弹窗工厂
function pop(type, content, color) {
switch (type) {
case 'infoPop':
return new infoPop(content, color)
case 'confirmPop':
return new confirmPop(content, color)
case 'confircancelPopmPop':
return new cancelPop(content, color)
}
}
ROOT.pop = pop
})(window)
// 根据传入不同的参数来,来弹出不同的弹窗
pop('infoPop', '开始', 'white')
上面这种写法有个弊端就是不能 new pop,当用户用 new 关键词就不适用了
;(function(ROOT) {
function pop(type, content, color) {
if (this instanceof pop) {
var s = this[type](content, color)
return
} else {
return new pop(type, content, color)
}
}
pop.prototype.infoPop = function() {}
pop.prototype.confirmPop = function() {}
pop.prototype.cancelPop = function() {}
ROOT.pop = pop
})(window)
pop('infoPop', '开始', 'white')
jQuery 源码示例
需求:jQuery 需要操作 dom,每个 dom 都是一个 jq 对象
总结:工厂模式就是要把我们要暴露的对象,真正要实例化的对象先封装到函数的内部,然后我们只暴露一个工厂方法,使用者通过这个工厂方法 来获取我们实例话的对象,它的优势方便我们大量的创建对象。
编写一个编辑器插件
需求:有一个编辑器插件,初始化的时候需要配置大量参数,而且内部功能很多
// 最终类
function Editor() {
this.intt = new initHTML()
this.fontControll = new fontControll()
this.stateControll = new stateControll()
}
// 初始化html的类,最终渲染成dom
function initHTML() {}
// 初始化控制样式的方法
initHTML.prototype.initStyle = function() {}
// 初始化渲染成dom的方法
initHTML.prototype.renderDom = function() {}
// 改变字体颜色大小的类
function fontControll() {}
// 控制颜色的方法
fontControll.prototype.changeColor = function() {}
// 控制字体大小的方法
fontControll.prototype.changeFontsize = function() {}
// 改变状态类,为了前进后退
function stateControll() {
this.state = [] //存储状态
this.nowstate = 0 //状态指针
}
//保存状态的方法
stateControll.prototype.saveState = function() {}
//回滚状态的方法
stateControll.prototype.saveBack = function() {
var state = this.state[this.nowstate - 1]
this.fontControll.changeColor(state.color)
this.fontControll.changeFontsize(state.color)
}
//前进状态的方法
stateControll.prototype.saveGo = function() {}
window.Editor = Editor
建造者模式是把它的模块抽离出独立的类,然后在组合起来
vue 初始化
需求:vue 内部众多模块,而且过程复杂,vue 类也可以看做是一个建造者模式
function Vue(options){
// 只允许用户用new操作符,如果直接调用就抛出警告
if(!(this instanceof Vue)){
console.warn('Vue is a constructor and should be called with the `new` keyword')
}
// 初始化配置项
this._init(options)
}
initMixin(Vue) // 初始化系统混入到Vue中去
stateMixin(Vue) // 状态系统混入到Vue中去
eventsMixin(Vue) // 事件系统的混入
lifecycleMixin(Vue) // 生命周期混入
renderMixin(Vue) // 渲染混入
通过这些方法和我们上一个案例把模块独立成一个类,把这些类放到暴露出的类里面是一个道理; 只不过这里改成方法,vue 中所有的功能都是独立开发,通过这一系列的混入将其混入进去
写一个数据存储对象
需求:项目中有一个全局的数据存储者,这个存储者只能有一个 ,不然会需要进行同步,增加复杂度
function store() {
this.state = {}
if (store.install) {
return store.install
}
store.install = this
}
store.install = null
var s1 = new store()
var s2 = new store()
s1.state.a = 1
console.log(s1, s2) // store { state: { a: 1 } } store { state: { a: 1 } }
vue-router
需求:vue-router 必须 保障全局有且只有一个,否则的话会错乱
let _Vue
function install(Vue) {
if (install.installed && _Vue === vue) return
install.installed = true
_Vue = vue
}
工厂模式:如果你写的模块,需要大量创建类似的对象
建造者模式:需要创建一个需要大量参数,且内部模块庞大
单例模式:防止重复注册,防止有多个对象互相干扰
什么是好的复用?
桥接模式
目的:通过桥接代替耦合
应用场景:减少模块之间的耦合
享元模式
目的:减少对象/代码数量
应用场景:当代码中创建了大量类似对象和类似的代码块
模版方法模式
目的:定义一系列操作的骨架,简化后面类似操作的内容
应用场景:当项目中出现很多类似操作内容
// 有三种形状,每种形状都有3种颜色
function rect(color) {
//矩形
showcolor(color)
}
function circle() {
// 圆形
showcolor(clor)
}
function delta(color) {
// 三角形
showcolor(clor)
}
new circle('red')
这个模式跟我们的建造者模式很类似拆分再组合,建造者模式的核心是如何去构造对象;而我们桥接模式是如何简化我们的代码,提高我们的可复用性,一个关注的是功能一个关注的是创建,这是它们的区别。
// 有一百种不同文字的弹窗,每种弹窗行为相同,但是文字和样式不同,我们没必要新间一百个弹窗对象
function Pop(){
}
// 保留同样的行为
Pop.prototype.action=function(){}
//显示
Pop.prototype.show=function(){}
// 提取出每个弹窗不同的部分作为一个外部数组
var popArr=[
{text:"window1",style:[400,400]}
{text:"window2",style:[400,200]}
]
var poper=new Pop()
for(var i=0;i<100;i++){
poper.show(popArr[i])
}
// 编写导航组件,有的带消息提示,有的竖着,有的横者
function baseNav() {
// 基础类,此处定下基本骨架
}
baseNav.prototype.action = function(fn) {
// 特异性的处理,留一个回调等待具体实现
}
创建不同的选中效果
需求:有一组菜单,上面每种选项都有不同的选中效果
// 一般做法
//menu1,menu2,menu3
function menuItem(word) {
this.word = ''
this.dom = document.createElement('div')
this.dom.innerHTML = this.word
}
var menu1 = new menuItem('menu1')
var menu2 = new menuItem('menu2')
var menu3 = new menuItem('menu3')
menu1.onmouseover = function() {
menu1.style.color = 'red'
}
menu2.onmouseover = function() {
menu1.style.color = 'green'
}
menu3.onmouseover = function() {
menu1.style.color = 'blue'
}
menu1.onmouseout = function() {
menu1.style.color = 'white'
}
menu2.onmouseout = function() {
menu1.style.color = 'white'
}
menu3.onmouseout = function() {
menu1.style.color = 'white'
}
// 桥接模式做法
function menuItem(word, color) {
this.word = word
this.color = color
this.dom = document.createElement('div')
this.dom.innerHTML = this.word
document.getElementById('app').appendChild(this.dom)
}
menuItem.prototype.bind = function() {
var self = this
this.dom.onmouseover = function() {
console.log(self.color)
this.style.color = self.color.colorOver
}
this.dom.onmouseout = function() {
this.style.color = self.color.colorOut
}
}
function menuColor(colorover, colorout) {
this.colorOver = colorover
this.colorOut = colorout
}
var data = [
{ word: 'menu1', color: ['red', 'black'] },
{ word: 'menu2', color: ['green', 'black'] },
{ word: 'menu3', color: ['blue', 'black'] }
]
for (var i = 0; i < data.length; i++) {
new menuItem(
data[i].word,
new menuColor(data[i].color[0], data[i].color[1])
).bind()
}
Express 中创建 get 等方法
需求:express 中 get,post 等方法,有七八个,如何快速地创建。
var methods = ['get', 'post', 'delete', 'put']
methods.forEach(function(method) {
app[method] = function() {
//利用桥接过来的route来完成post,get等请求
route[method].apply(route, slice.call(arguments, 1))
}
})
文件上传
需求:项目中有一个文件上传功能,该功能可以上传多个文件
一般做法
//文件上传
function uploader(fileType, file) {
this.fileType = fileType
this.file = file
}
uploader.prototype.init = function() {
//初始化文件上传的html
}
uploader.prototype.delete = function() {
//删除掉该html
}
uploader.prototype.uploading = function() {
//上传
}
new uploader('img', fileob1)
new uploader('txt', fileob2)
new uploader('img', fileob3)
new uploader('word', fileob4)
享元模式
//fileType,file
function uploader() {}
uploader.prototype.init = function() {
//初始化文件上传的html
}
uploader.prototype.delete = function() {
//删除掉该html
}
uploader.prototype.uploading = function(filetype, file) {}
var fileob1, fileob2, fileob3, fileob4
var data = [
{
type: 'img',
file: fileob1
},
{
type: 'txt',
file: fileob2
},
{
type: 'img',
file: fileob3
},
{
type: 'word',
file: fileob4
}
]
var uploader = new uploader()
for (var i = 0; i < data.length; i++) {
uploader.uploading(data[i].type, data[i].file)
}
jQuery 的 extend
需求:extends 方法,需要判断参数数量来进行不同的操作
// 一般做法
var jQuery = {}
jQuery.fn = {}
jQuery.extend = jQuery.fn.extend = function() {
if (arguments.length == 1) {
for (var item in arguments[0]) {
this[item] = arguments[0][item]
}
} else if (arguments.length == 2) {
for (var item in arguments[1]) {
arguments[0][item] = arguments[1][item]
}
return arguments[0]
}
}
// 享元做法,保留一个公共的for循环
jQuery.extend = jQuery.fn.extend = function() {
var target = arguments[0]
var source
if (arguments.length == 1) {
target = this
source = arguments[0]
} else if (arguments.length == 2) {
target = arguments[0]
source = arguments[1]
}
for (var item in source) {
target[item] = source[item]
}
return target
}
编写一个弹窗组件
需求:项目有一系列弹窗,每个弹窗的行为、大小、文字不同
function basePop(word, size) {
this.word = word
this.size = size
this.dom = null
}
basePop.prototype.init = function() {
var div = document.createElement('div')
div.innerHTML = this.word
div.style.width = this.size.width + 'px'
div.style.height = this.size.height + 'px'
this.dom = div
}
basePop.prototype.hidden = function() {
//定义基础操作
this.dom.style.display = 'none'
}
basePop.prototype.confirm = function() {
//定义基础操作
this.dom.style.display = 'none'
}
function ajaxPop(word, size) {
basePop.call(this, word, size)
}
ajaxPop.prototype = new basePop()
var hidden = ajaxPop.prototype.hidden
ajaxPop.prototype.hidden = function() {
hidden.call(this)
console.log(1)
}
var confirm = ajaxPop.prototype.confirm
ajaxPop.prototype.confirm = function() {
confirm.call(this)
console.log(1)
}
var pop = new ajaxPop('sendmes', { width: 100, height: 300 })
pop.init()
pop.confirm()
var axios = {
get: function() {
return Promise.resolve()
}
}
上面这个就是我们面向对象中的继承,模版模式不一定非要通过继承方式来完成,它强调先定义后面进行不同维度的操作的基本行为;
然后在这个基本行为上有扩展的空间,这就是我们模版方法的目的。
封装一个算法计算器
需求:现在我们有一系列自己的算法,但是这个算法常在不同的地方需要增加一些不同的操作
//算法计算器
function counter() {
this.beforeCounter = []
this.afterCounter = []
}
//然后我们把具体的不同部分留到具体使用的时候去扩展
//所以我们定义两个方法来扩展
counter.prototype.addBefore = function(fn) {
this.beforeCounter.push(fn)
}
counter.prototype.addAfter = function(fn) {
this.afterCounter.push(fn)
}
//最终计算方法
counter.prototype.count = function(num) {
//结果变量
var _resultnum = num
//算法队列数组组装
var _arr = [baseCount]
_arr = this.beforeCounter.concat(_arr)
_arr = _arr.concat(this.afterCounter)
//不同部分的相同算法骨架
function baseCount(num) {
num += 4
num *= 2
return num
}
//循环执行算法队列
while (_arr.length > 0) {
_resultnum = _arr.shift()(_resultnum)
}
return _resultnum
}
//使用
var countObject = new counter()
countObject.addBefore(function(num) {
num--
return num
})
countObject.addAfter(function(num) {
num *= 2
return num
})
countObject.count(10)
这个应用了组合实现模版模式
javascript 的组合与继承
组合(推荐)
继承
通过独立方法间接的桥接来形成整体功能,这样每个方法都可以被高度复用
提取出公有部分与私有部分,私有部分作为外部数据传入,从而减少对象数量
当一个功能朝着多样化发展,不妨先定义一个基础的,把具体实现延迟到后面
提高可扩展性的目的
什么是好的扩展
适配器模式的目的:通过写一个适配器,来代替替换
适配器模式的应用场景:面临接口不通用的问题
装饰者模式的目的:不重写方法的扩展方法
装饰者模式的应用场景:放一个方法需要扩展,但是又不好去修改方法
命令模式的目的:解耦实现和调用,让双方互不干扰
命令模式的应用场景:调用的命令充满不确性
var log = function() {
return winodw.console.log
}
想用 log 来代替 console.log
// 在一个他人写好的模版a内部调用b,模块为他人写好,不能修改,如何扩展b方法?
var a = {
b: function() {}
}
function myb() {
a.b()
// 要扩展的方法
}
我们新建一个自己的方法,在其内部调用 b 方法,并且再执行自己的方法,这样可以在不修改原方法的情况下扩展方法
var command=(function(){
// action中定义了各种方法
var action={}
// excure可以调用action方法
return function excure()
})()
// command只需输入命令就可以调用action里的方法
框架的变更
需求:目前项目使用的 A 框架,现在改成了 B,2 个框架与十分类似,但是有少数几个方法不同
// A框架调用的方式
A.c()
// 假如我们项目中换成了jQuey,我们不想全部去替换A方法,就用适配器的方法
A.c = function() {
return $.on.apply(this.arguments)
}
参数适配
需求:为了避免参数不适配产生问题,很多框架会有一个参数适配操作
// 给参数适配,没传值给默认值
function f1() {
var _defalut = {
name: '',
color: ''
}
for (var item in _defalut) {
_defalut[item] = config[item] || _defalut[item]
}
return _defalut
}
扩展你的已有事件绑定
需求:现在项目改造,需要给 input 标签已经有的事件增加一些操作
var decorator = function(dom, fn) {
if ((typeof dom.onclick = 'function')) {
var _old = dom.onclick
dom.onclick = function() {
_old()
fn()
}
}
}
decorator(document.getElementById('dom1'), function() {
// 自己的操作
})
Vue 的数组监听
需求:vue 中利用 defineProperty 可以监听对象,那么数组怎么办
var arrayProto = Array.prototype
var arrayMethods = Object.create(arrayProto)
var methodsToPatch = [
'push',
'pop',
'unshift',
'shift',
'splice',
'resverse',
'sort'
]
methodsToPatch.forEach(method => {
var original = arrayMethods[method]
object.defineProperty(arrayMethods, method, {
value(...args) {
const result = original.apply(this, args)
dep.notify()
return result
}
})
})
装饰者模式,拿到老方法,调用老方法,组成新方法
需求:封装一系列的 canvas 绘图命令
var myCanvas = function() {}
myCanvas.prototype.drawCircle = function() {}
myCanvas.prototype.drawRect = function() {}
var canvasComand = (function() {
var action = {
drawCircle: function(config) {},
drawRect: function(config) {}
}
return function excute(commander) {
commander.forEach(item => {
action[item.command](item.config)
})
}
})()
myCanvas([{ command: 'drawReact', config: {} }])
用户只管输入他要的命令,不用关心 api
命令和实现解耦,无论命令发生变动,还是实现发生变动,都不会彼此影响
绘制随数量图片
需求:需要做一个画廊,图片数量和排列顺序随机
var createImg = (function() {
var action = {
create: function(obj) {
var htmlArr = []
var _htmlstring = ''
var _htmlTemplate =
"{{title}}
"
var displayWay = {
normal: function(arr) {
return arr
},
reverse: function(arr) {
return arr.reverse
}
}
obj.imgArr.forEach(img => {
var _html
_html = _htmlTemplate
.replace('{{img-url}}', img.img)
.replace('{{title}}', img.title)
htmlArr.push(_html)
})
htmlArr = displayWay[obj.type](htmlArr)
_htmlstring = htmlArr.join('')
return '' + _htmlstring + ''
},
display: function(obj) {
var _html = this.create(obj)
obj.target.innerHTML = _html
}
}
return function excute(obj) {
var _default = {
imgArr: [{ img: 'xxxx', title: 'default title' }],
type: 'normal',
target: document.body
}
for (var item in _default) {
_default[item] = obj[item] || _default[item]
}
action.display(_default)
}
})()
createImg({
imgArr: [
{ img: 'xxxx', title: 'default title1' },
{ img: 'xxxx', title: 'default title2' }
],
type: 'normal'
})
数据-> excute 命令解析层 -> 调用 api
当面临两个新老模块间接口 api 不匹配,可以用适配来转化 api
当老的方法,不方便去直接修改,可以通装饰者来增加功能
解耦实现与具体命令,让实现端和命令端扩展的都更轻松
提高整体项目可扩展性的核心
低耦合
良好的组织沟通方式
观察者模式的目的:减少对象间的耦合,来提高可扩展性
观察者模式的应用场景:当两个模块直接沟通会增加它们的耦合性时
职责链模式的目的:为了避免请求发送者与多个请求处理者耦合在一起,形成一个链条
组合模式的应用场景:把操作分隔成一系列模块,每个模块只处理自己的事情
访问者模式的目的:解耦数据结构与数据操作
访问者模式的应用场景:数据结构不希望与操作有关联
function observe {
this.message={}
}
observe.prototype.regist=function(type,fn) {
this.message[type]=fn
}
observe.prototype.fire=function(type){
this.message[type]()
}
observe.prototype.remove=function(type){
this.message[type]=null
}
function mode1() {}
function mode2() {}
function mode3() {}
_result = mode1(_result)
_result = mode2(_result)
_result = mode3(_result)
var data = []
var handler = function() {}
handler.prototype.get = function() {}
var vistor = function(handler, data) {
handler.get(data)
}
多人合作的问题
需求:现在假设 A 工程师写了首页模块,然后 B 工程师写了评论模块。现在要把评论展示在首页
function observe {
this.message={}
}
observe.prototype.regist=function(type,fn) {
this.message[type]=fn
}
observe.prototype.fire=function(type){
this.message[type]()
}
function comment () {
var self=this;
this.commentList=[
{
type:'hot',
content:'xxxx'
}
];
// 注册事件
observeOb.regist('gotHot',function(){
var _arr=[];
self.commentList.forEach((item)=>{
if(item.type==='hot'){
_arr.push(item)
}
})
return _arr
})
}
// 调用事件
var _arr=observeOb.fire('gotHot')
一个转盘
需求:有一个转盘应用,每转一圈,速度加快
function observe {
this.message={}
}
observe.prototype.regist=function(type,fn) {
this.message[type]=fn
}
observe.prototype.fire=function(type){
this.message[type]()
}
var observeOb=new observe()
// 初始化html=> 最终结果选定 => 运动结果 => 运动控制
var _domArr=[]
function htmlInit (target) {
for(let i =0;i<9;i++){
var _div=document.createElement('div')
_div.innerHTML=i
_div.setAttribute('class','item')
target.appendChild(_div)
_domArr.push(_div)
}
}
function getFinal(){
var _num=Math.random()*10+40
return Math.floor(_num,0)
}
// 运动模块
function mover (moveConfig){
var nowIn=0;
var removeNum=9;
var timer=setInterval(()=>{
if(nowIn!=0){
removeNum=nowIn-1
}
_domArr[removeNum].setAttribute('class','item')
_domArr[nowIn].setAttribute('class','item item-on')
nowIn++
if(nowIn==moveConfig.moveTime){
clearInterval(timer)
if(moveConfig.moveTime==10){
observeOb.fire('finish')
}
}
},moveConfig.speed)
}
function moveControll () {
var final=getFinal()
var _circle=Math.floor(final/10,0)
var stopNum=final%10
var _speed=2000
var _runCircle=0
mover({
moveTime:10,
speed:_speed
})
observeOb.regist('finish',fucntion(){
var _time=0;
_speed-=50;
_runCircle++;
if(_runCircle<=_circle){
_time=0
}else {
_time=stopNum
}
mover({
moveTime:_time,
speed:_speed
})
})
}
htmlInit(document.getElementById('app'))
moveControll()
Axios 的拦截器
需求:axios 拦截器的设计,大家可以看成一个用给职责链的思想去处理请求
function axios() {
this.interceptors = {
request: new interceptorsManner(),
response: new interceptorsManner()
}
axios.prototype.request = function() {
var chain = [dispathReuest, undefined]
var promise = Promise.resolve(config)
this.interceptors.request.handlers.forEach(interceptor => {
chain.unshift(interceptor.fulfilled, interceptor.injected)
})
this.interceptors.response.handlers.forEach(interceptor => {
chain.shift(interceptor.fulfilled, interceptor.injected)
})
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift())
}
}
}
function interceptorsManner() {
this.handlers = []
}
interceptorsManner.prototype.use = function(fulfilled, rejected) {
this.handlers.push({
fulfilled: fulfilled,
rejected: rejected
})
}
利用职责链组织一个表单验证
需求:有一个表单,需要前后台校验,后台校验
// 表单事件绑定->表单前端验证->表单后端验证
// 思想:把你要做的事情拆分为模块,模块之间只做自己模块的事情
input.onblur = function() {
var _value = input.value
var _arr = [font, middle, back, fontAgain]
async function test() {
var _result = _value
while (_arr.length > 0) {
_result = await _arr.shift()(_result)
}
return _result
}
test().then(res => {
console.log(res)
})
}
function font(result) {}
function middle(result) {}
function back(result) {}
function fontAgain(result) {}
不同角色访问数据
需求:假设有一个公司的财务报表,财务关心支出和收入,老板关心盈利
function report() {
this.income = ''
this.cost = ''
this.profit = ''
}
function boss() {}
boss.prototype.get = function(data) {}
function account() {}
account.prototype.get = function(num1, num2) {}
function vistor(data, man) {
var handle = {
boss: function(data) {
man.get(data.profit)
},
account: function(data) {
man.get(data.income, data.cost)
}
}
handle[man.constructor.name](data)
}
vistor(new report(), new account())
vistor(new report(), new boss())
// 设计的数据结构操作难以去访问具体的数据结构的时候
表格操作
需求:一个可以新增,删除的表格
function table () { }
table.prototype.show=function () {
}
table.prototype.delete=function () {
vistor(this.tableData,'delete',id)
}
table.prototype.add=function () {
}
var tableData=[
{
id:1,
name:'xxx',
prize:'xxx'
}
]
function vistor (table,data,handle) {
var handleOb={
delete:function(id){
},
add:funtion(id,name,price){
}
}
var arg=Array.prototype.splice(arguments);
arg.splice(0,3);
handleOb[handle].apple(this,arg)
}
适用于不适合直接沟通的模块之间的组织
组织同步模块,把要做的事情划分为模块,要做的事情一次传递
解耦数据操作与数据结构
提高代码质量
什么是代码质量
策略/状态模式的目的:优化 if-else 分支
策略/状态模式的应用场景:当代码 if-else 分支过多时
外观模式的目的:通过为多个复杂的子系统提供一个一致的接口
外观模式的应用场景:当完成一个操作时,需要操作多个子系统,不如提供一个更高级的
迭代器者模式的目的:不访问内部的情况下,方便的遍历数据
迭代器模式的应用场景:当我们需要对某个对象进行操作,但是又不能暴露内部
备忘录模式的目的:记录状态,方便回滚
备忘录模式的应用场景:系统状态多样,为了保证状态的回滚方便,记录状态
假设要编写一个计算器,有加减乘除,我们把一层一层的 if 判断,变成下面的形式
function Strategy (type,a,b) {
var Strategyer={
add:function (a,b){
return a+b
}
minus:function(a,b){
return a-b
}
division:function (a,b){
return a/b
}
}
return Strategyer[type](a,b)
}
为了减少 if-else 结构,将判断变成对象内部的一个状态,通过对象内部的状态改变,让其拥有不同的行为
function stateFactor(state) {
var stateObject = {
_status: '',
state: {
state1: function() {},
state2: function() {}
},
run: function() {
return this.state[this._status]
}
}
stateObject._status = state
return stateObject
}
我们组织方法模块时可以细化多个接口,但是我们给别人使用时,要合为一个接口就像你可以直接去餐厅去点套餐
// 模块1
function Model1() {}
// 模块2
function Model2() {}
// 功能由Model1获取Model2得结果来完成
function use() {
Model2(Model1())
}
在不暴露对象内部结构的同时,可以顺序的访问对象内部,可以帮助我们简化循环,简化数据操作
function Iterator(item) {
this.item = item
}
Iterator.prototype.dealEach = function(fn) {
for (var i = 0; i < this.item.length; i++) {
fn(this.item[i], i)
}
}
记录对象内部的状态,当有需要时回滚到之前的状态或者方便对象使用
function Memento() {
var cache = {}
return function(cacheName) {
if (cache[cacheName]) {
// 有缓存的操作
} else {
// 没有缓存的操作
}
}
}
var MementtoFn = Memento()
MementtoFn('xxxcx')
动态的内容
需求:项目有一个动态的内容,根据用户权限的不同显示不同的内容
// 没有用策略的模式的情况
function showPart1() {
console.log(1)
}
function showPart2() {
console.log(2)
}
function showPart3() {
console.log(3)
}
axios.get('xxx').then(res => {
if (res == 'boss') {
showPart1()
showPart2()
showPart3()
} else if (res == 'manner') {
showPart1()
showPart2()
} else if (res == 'staff') {
showPart3()
}
})
// 用策略模式的情况
function showControl() {
this.status = ''
this.power = {
boss: function() {
showPart1()
showPart2()
showPart3()
},
manner: function() {
showPart1()
showPart2()
},
staff: function() {
showPart3()
}
}
}
showControl.prototype.show = function() {
var self = this
axios.get('xxx').then(res => {
self.status = res
self.power[self.status]()
})
}
new showControl().show()
复合运动
需求:有一个小球,可以控制它左右移动,或则左前,右前等方式移动
function moveLeft() {
console.log('left')
}
function moveRight() {
console.log('RigmoveRight')
}
function moveTop() {
console.log('Top')
}
function moveBottom() {
console.log('bomoveBottom')
}
// 没有用状态模式的情况
function mover() {
if (arguments.length == 1) {
if (arguments[0] == 'left') {
moveLeft()
} else if (arguments[0] == 'right') {
moveRight()
} else if (arguments[0] == 'top') {
moveTop()
} else if (arguments[0] == 'bottom') {
moveBottom()
}
} else {
if (arguments[0] == 'left' && arguments[1] == 'top') {
moveLeft()
moveTop()
} else if (arguments[0] == 'right' && arguments[1] == 'bottom') {
moveRight()
moveBottom()
}
}
}
// 用状态模式的情况
function mover() {
this.status = []
this.actionHandle = {
left: moveLeft,
right: moveRight,
top: moveTop,
bottom: moveBottom
}
}
mover.prototype.run = function() {
this.status = Array.prototype.slice.call(arguments)
this.status.forEach(action => {
this.actionHandle[action]()
})
}
new mover().run('left', 'right')
插件封装的规律
需求:插件基本都会给最终使用提供一个高级接口
// 划分功能,给使用者一个统一的接口
function tab() {
this.dom = null
}
tab.prototype.initHTML = function() {}
tab.prototype.changeTab = function() {}
tab.prototype.eventBind = function() {
var self = this
this.dom.onclick = function() {
self.changeTab()
}
}
tab.prototype.init = function(config) {
this.initHTML(config)
this.eventBind()
}
封装成方法的思想
需求:在兼容时代,我们会常常需要检测能力,不妨作为一个统一接口
//dom支持检测
function addEvent(dom, type, fn) {
if (dom.addEventListener) {
dom.addEventListener(type, fn, false)
} else if (dom.attachEvent) {
dom.attachEvent('on' + type, fn)
} else {
dom['on' + type] = fn
}
}
构建一个自己的 forEach
需求:forEach 方法其实是一个典型的迭代器方法
// 对数组和对象进行迭代
//forEach
function Iterator(data) {
this.data = data
}
Iterator.prototype.dealEach = function(fn) {
if (this.data instanceof Array) {
for (var i = 0; i < this.data.length; i++) {
fn(this.data[i], i)
}
} else {
for (var item in this.data) {
fn(this.data[item], item)
}
}
}
给你的项目数据添加迭代器
需求:项目经常对于后端数据进行遍历操作,不如封装一个迭代器,遍历的更方便
//数据迭代器
var data = [{ num: 1 }, { num: 2 }, { num: 3 }]
function i(data) {
function Iterator(data) {
this.data = data
}
Iterator.prototype.getHasSomenum = function(handler, num) {
var _arr = []
var handleFn
if (typeof handler == 'function') {
handleFn = handler
} else {
handleFn = function(item) {
if (item[handler] == num) {
return item
}
}
}
for (var i = 0; i < this.data.length; i++) {
var _result = handleFn.call(this, this.data[i])
if (_result) {
_arr.push(_result)
}
}
return _arr
}
return new Iterator(data)
}
i(data).getHasSomenum('num', 1)
// 自定义的筛选方法
i(data).getHasSomenum(function(item) {
if (item.num - 1 == 2) {
return item
}
})
文章页的缓存
需求:项目有一个文章页需求,现在进行优化,如果上一篇已经读取过了,则不进行请求,否则请求文章数据
//缓存
function pager() {
var cache = {}
return function(pageName) {
if (cache[pageName]) {
return cache[pageName]
} else {
axios.get(pageName).then(res => {
cache[pageName] = res
})
}
}
}
var getpage = pager()
getpage('pageone')
前进后退功能
需求:开发一个可移动的 div,拥有前进后退功能能回滚到之前的位置
//前进后退
function moveDiv() {
this.stateList = [] // 缓存之前的状态
this.nowState = 0 // 状态指针,指向当前状态在哪个
}
/**
* @param {string} type 往哪个方向移动
* @param {Number} num 移动多远
*/
moveDiv.prototype.move = function(type, num) {
changeDiv(type, num) //假设移动位置的函数
this.stateList.push({
type: type,
num: num
}) // 添加状态
this.nowState = this.stateList.length - 1 // 设置当前指针
}
moveDiv.prototype.go = function() {
//前进
var _state
if (this.nowState < this.stateList.length - 1) {
//当前指针小于数组最后一位,说明能前进
this.nowState++
_state = this.stateList[this.nowState]
changeDiv(_state.type, _state.num)
}
}
moveDiv.prototype.back = function() {
//后退
var _state
if (this.nowState >= 0) {
this.nowState--
_state = this.stateList[this.nowState]
changeDiv(_state.type, _state.num)
}
}
帮助我们优化 if-else 结构
一种套餐化接口的思想,提醒我们封装常用方法
帮助我们更好的遍历数据
帮我们缓存以及回到过去的状态
提示
我们的设计模式,要记住其思想,不用记住其结构,结构不是固定;我们通过设计模式主要是提高我们代码的质量