设计模式,从设计到模式
设计:设计原则(统一指导思想)
模式:通过概念总结出的一些模板,可以效仿的固定式的东西(根据指导思想结合开发经验,总结出固定的样式或模板)
按类型分
创建型(对象的创建及生成)
image.png
组合型(对象和类是怎样的组合形式,一个类不一定能满足需求,通过组合的形式完成)
image.png
行为型(涵盖了开发中的一些常用的行为,如何设计才能满足需求)
image.png
image.png
工厂模式(实例化对象模式)
image.png
image.png
demo
//Creator是个工厂,有个create函数,工厂通过create创建Product class Product { constructor(name) { this.name = name } init() { alert(`${this.name}`) } fn1() { alert('this is fn1') } fn2() { alert('this is fn2') } } class Creator { create(name) { // 生成类的一个实例 return new Product(name) } } // 测试 // 生成一个工厂 let creator = new Creator() // 通过工厂省城product的实例 let p = creator.create('p') p.init() p.fn1() let p1 = creator.create('p1') p1.init() // 通过工厂模式将函数封装好,暴露一个接口即可
class jQuery { constructor(selector) { // 获取数组的slice let slice = Array.prototype.slice // 获取节点,利用slice.call将其结果返回给一个数组,因为可能是多个dom节点 let dom = slice.call(document.querySelectorAll(selector)) // 获取dom的长度 let len = dom ? dom.length : 0 // 进行循环 for (let i = 0; i < len; i++) { // 将dom的数组元素赋值给this也就是实例的元素,元素的k就是数组的k,0,1,2... this[i] = dom[i] } // 赋值数组的长度 this.length = len this.selector = selector || '' } append(node) { //... } addClass(name) { //... } html(data) { //... } // 此处省略若干 API}// 这个函数相当于工厂,封装了返回实例的操作// 入口,这个$是个函数,函数里面返回一个jquery实例window.$ = function(selector) { return new jQuery(selector) }console.log($(p))
题外话:读源码lib的意义
1、学习如何实现功能
2、学习设计思路
3、强制模拟好的代码
设计原则验证
1、构造函数和创建者分离
2、符合开放封闭原则
单例模式
介绍
系统中被唯一使用
一个类只有一个实例(在内部使用实例,不可以在外部使用)
举例
登录框
购物车
说明
js中没有private这个私有属性关键字,typescript除外
java代码实例
image.png
image.png
js代码实例
// js中使用单例模式 class SingleObject { // 在这里面定义的方法非静态,初始化实例时,都会有login()这个方法 login() { console.log('login...') } } // 定义一个静态的方法,将方法挂载到class上面,无论SingleObject被new多少个,getInstance的方法只有一个 SingleObject.getInstance = (function() { let instance return function() { // 如果没有则赋值,初始化 if (!instance) { instance = new SingleObject(); } // 有的话直接返回 return instance } })() // js中的单例模式只能靠文档去约束,不能使用private关键字去约束 // 测试:注意这里只能使用静态函数getInstance,不能使用new SingleObject() let obj1 = SingleObject.getInstance() obj1.login() let obj2 = SingleObject.getInstance() obj2.login() // 单例模式(唯一的),每次获取的都是一个东西,所以他两三等,否则就不是单例模式 console.log(obj1 === obj2) //true
jquery中的$是单例模式
// 不管用几个jquery文件,他自字内部会处理,引用同一个$ // 单例模式的思想,如果有直接用,没有的话,实例化一个 if(window.jQuery != null){ return window.jQuery }else{ // init }
模拟登录框
class LoginForm { constructor() { // 初始化属性,默认是隐藏的 this.state = "hide" } show() { // 当前是show,alert已经显示 if (this.state == "show") { alert('已经显示') // return 不在执行了 return } // 如果不等于show,就继续执行,让他等于show this.state = "show" alert('登录框显示成功') } hide() { if (this.state = "hide") { alert('已经隐藏') return } this.state = "hide" alert('登录框隐藏') } } // 自定义函数在这里定义的目的,就是为了加一个闭包的变量instace,防止变量污染 LoginForm.getInstance = (function() { let instace // 这里函数目的在于获取单例的变量 return function() { if (!instace) { instace = new LoginForm() } return instace } })() // 测试 // 模拟第一个页面的登录框让其显示 let login1 = LoginForm.getInstance() login1.show() // 模拟第二个页面的登录框让其隐藏 let login2 = LoginForm.getInstance() login2.show()
vue中的store也是单例模式
设计原则验证
1、符合单一职责原则,只实例化唯一的对象
2、没法具体体现开放封闭原则,但是绝不违反开放封闭原则
适配器模式
介绍
旧接口格式和使用者不兼容
中间加一个适配转换接口
js代码演示
// 旧接口 class Adaptee{ specificRequest(){ return '这是旧接口' } } // 新接口 class Target{ constructor(){ this.adaptee = new Adaptee() } request(){ // 进行转换 let info = this.adaptee.specificRequest() return `${info}--新接口` } } // 测试 let target = new Target() console.log(target.request())
封装旧接口(旧接口和新接口起冲突时,使用适配器修改)
image.png
image.png
vue中的computed就是适配模式
设计原则验证
1、将旧接口和使用者进行分离(所有分离和解耦的方式都属于开放封闭原则)
2、符合开放封闭原则
装饰器模式(既能使用原有的功能,又能使用装饰后的功能)
介绍
为对象添加新功能
不改变其原有的结构和功能
举例
比如手机可以打电话、发短信,我们在原有的基础上装个保护壳,防止摔落时损坏
js代码演示
class Circle { draw() { console.log('我要画一个圆') } } class Decorator { constructor(circle) { this.circle = circle } draw() { this.circle.draw() this.setBorder(circle) } setBorder(circle) { console.log('我还画了条线') } } // 测试 // 想引用某个类的时候将他实例化,以参数的形式进行传递进去 let circle = new Circle() circle.draw() console.log('+++++') let dec = new Decorator(circle) dec.draw()
ES7装饰器
考虑到浏览器兼容问题,需要安装插件支持
1、npm install babel-plugin-transform-decorators-legacy --save-dev
2、对babel进行配置
image.png
装饰类
// 1、首先定义一个类// 2、定义一个函数,传个target参数// 3、在Demo这个类上面,加个@testDemo(这个就是装饰器),通过@语法将这个类装饰一遍,target这个参数其实就是Demo这个类@testDemoclass Demo{ }function testDemo(target){ target.isDec = true} alert(Demo.isDec)// +++++++++++++++++++++++++++++++// 装饰器原理/*@decoratorclass A {}*/// 等同于class A {}// 将A定义成decorator函数执行一遍的返回值(相当于A在decorator执行了一遍),没有的话返回AA = decorator(A) || A// +++++++++++++++++++++++++++++++// 可以加个参数@testDemo1(false)class Demo1{ }function testDemo1(isDec){ // 这里面返回一个函数,装饰器返回的都是一个函数 return function(target){ target.isDec = isDec } } alert(Demo1.isDec)
装饰类—mixinshi示例
// 先定义一个mixins,获取解构的集合...listfunction mixins(...list) { // return一个函数,装饰器都是一个函数 return function (target) { // 将target.prototype和...list集合 混合在一起 Object.assign(target.prototype, ...list) } }// 混合的对象const Foo = { foo() { alert('foo') } }// 加个mixins的装饰器,将Foo对象传递进去// @mixins(Foo) 一执行,最后返回的是个函数,通过@关键字使用装饰器,将Foo里面的属性和并到target.prototype上@mixins(Foo)class MyClass {} // MyClass()里面没有Foo,通过装饰器将他们合并便具有了foo()方法let obj = new MyClass(); obj.foo() // 'foo'
装饰方法—示例1
// 所有装饰器都是一个函数,最后将其返回/* @param target——Person这个类 @param name——当前属性点 @param descriptor——属性描述 */function readonly(target, name, descriptor){ // descriptor对象原来的值如下 // { // value: specifiedFunction, // enumerable: false, // configurable: true, // writable: true // }; // 将可写的关闭 descriptor.writable = false; // 将其返回,所有被@readonly装饰过的只可读不可写 return descriptor; }class Person { constructor() { this.first = 'A' this.last = 'B' } @readonly // name()是Person里面的一个方法,我们想要只能获取不能修改,所以加个@readonly装饰器 name() { return `${this.first} ${this.last}` } }var p = new Person()console.log(p.name()) p.name = function () {} // 这里会报错,因为 name 加了装饰器,就是只读属性
装饰方法—示例2
function log(target, name, descriptor) { // 获取value,其实就是add函数 var oldValue = descriptor.value;// 将value重新赋值一个函数 descriptor.value = function() { console.log(`Calling ${name} with`, arguments); // 将原本的函数执行一下也就是add(),apply改变this的指向 return oldValue.apply(this, arguments); }; return descriptor; }// 定义一个Math类,希望在执行add执行的时候打印一个日志class Math { // 被装饰过的add()就是一个行的value,新的value就是先打印日志,再去做之前加法的执行,最后将其返回 @log add(a, b) { return a + b; } }const math = new Math();// 执行 add 时,会自动打印日志,因为有@log装饰器const result = math.add(2, 4);console.log('result', result);
第三方库core-decorators(提供常用的装饰器)
1、安装npm i core-decorators --save
2、直接引用,就不需要自己写了
import {readonly} from "core-decorators" class Person { @readonly name(){ return 'this is test' } }let p = new Person()// 可读不可写 alert(p.name())
// 此装饰器提醒用户方法已废弃import { deprecate } from 'core-decorators';class Person { @deprecate('这个里面可以自己添加提示语') facepalm() {} @deprecate('We stopped facepalming') facepalmHard() {} @deprecate('We stopped facepalming', { url: 'http://knowyourmeme.com/memes/facepalm' }) facepalmHarder() {} } let person = new Person(); person.facepalm();// DEPRECATION Person#facepalm: This function will be removed in future versions.person.facepalmHard();// DEPRECATION Person#facepalmHard: We stopped facepalmingperson.facepalmHarder();// DEPRECATION Person#facepalmHarder: We stopped facepalming//// See http://knowyourmeme.com/memes/facepalm for more details.
设计原则验证
1、将现有对象和装饰器进行分离,两者独立存在(解耦)
2、符合开放封闭原则
代理模式
介绍
使用者无权访问目标对象
中间加代理,通过代理做授权和控制
js代码演示
class ReadImg{ constructor(filename){ this.filename = filename this.loadImg() } display(){ console.log('display---'+ this.filename) } loadImg(){ console.log('loading---'+this.filename) } }class ProxyImg{ constructor(filename){ this.readImg = new ReadImg(filename) } display(){ this.readImg.display() } }let proxyImg = new ProxyImg('1.png') proxyImg.display()
使用场景
1、网页事件代理
<!DOCTYPE html><html><head> <title></title> <style type="text/css"> *{ padding: 0; margin: 0; } ul li{ list-style: none; } </style></head><body><ul id="ul"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li></ul></body><script type="text/javascript"> let ul = document.getElementById('ul') ul.addEventListener('click',function(e){ if(e.target.nodeName === "LI"){ alert(e.target.innerHTML) } })</script></html>
2、this的指向
<!DOCTYPE html><html><head> <title></title> <style type="text/css"> *{ padding: 0; margin: 0; } ul li{ list-style: none; } </style></head><body><ul id="ul"> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li></ul></body><script type="text/javascript"> let ul = document.getElementById('ul') ul.addEventListener('click',function(){ var _this = this; // 箭头函数里面this的执行是window,所以利用代理的方式将其值赋给_this setTimeout(function(){ _this.style.backgroundColor = 'red' },2000) })</script></html>
3、$.proxy
image.png
4、ES6 Proxy
// 明星let star = { name: '张XX', age: 25, phone: '13910733521'}// 经纪人agent // star 代理的对象,监听代理的获取 get 和设置 set 属性// 注意代理的接口要和原生的一样 比如要知道name,就写namelet agent = new Proxy(star, { // target代理对象, key就是代理对象的值 get: function (target, key) { if (key === 'phone') { // 返回经纪人自己的手机号 return '18611112222' } if (key === 'price') { // 明星不报价,经纪人报价 return 120000 } // 如果不是在这个两种情况,直接返回target[key] return target[key] }, set: function (target, key, val) { // 这是我们自己定义的价格 if (key === 'customPrice') { if (val < 100000) { // 最低 10w,小于10万,报错 throw new Error('价格太低') } else { target[key] = val // 这里写renturn true 要不然不会赋值成功 return true } } } })// 测试+++++++++++// 主办方console.log(agent.name)console.log(agent.age)console.log(agent.phone)console.log(agent.price)// 想自己提供报价(砍价,或者高价争抢)agent.customPrice = 150000// agent.customPrice = 90000 // 报错:价格太低console.log('customPrice', agent.customPrice)
设计原则验证
1、代理类和目标类分离,隔离开目标类和使用者
2、符合开放封闭原则
代理模式、适配模式、装饰器模式三者的区别
适配器:提供一个不同的接口(进行格式的转换)
代理模式:提供一模一样的接口(无权使用主类,所以进行代理,提供一个一模一样的接口)
装饰器模式:扩展功能,原有功能不变且可直接使用
代理模式:显示原有功能,但是经过限制或阉割之后的
外观模式
介绍
为子系统中的一组接口提供了一个高层接口
使用者使用了这个高层接口
image.png
场景
传多个参数都可适用
image.png
设计原则验证
1、不符合单一职责和开放封闭原则,因此谨慎使用,不可滥用(不要为了设计而设计,而是为了使用而设计)
观察者模式
介绍
发布 & 订阅(定好东西,付了款,会有人上门送,比如订牛奶、报纸啊等)
一对多(N)(可以同时订购牛奶,报纸,两者之间没什么冲突)
js代码
// 主题,保存状态,接收状态变化,状态变化后触发所有观察者对象class Subject { constructor() { // 状态 this.state = 0 // 所有观察者为一个数组 this.observers = [] } getState() { return this.state } setState(state) { this.state = state this.notifyAllObservers() } // 添加一个新的观察者 attach(observer) { this.observers.push(observer) } // 循环所有的观察者 notifyAllObservers() { this.observers.forEach(observer => { // 遍历的每个元素执行update方法 observer.update() }) } }// 观察者,等待被触发class Observer { constructor(name, subject) { this.name = name this.subject = subject // 将自己添加进去,把观察者添加到主题当中 this.subject.attach(this) } update() { console.log(`${this.name} update, state: ${this.subject.getState()}`) } }// 测试代码let s = new Subject()let o1 = new Observer('o1', s)let o2 = new Observer('o2', s)let o3 = new Observer('o3', s) s.setState(1) s.setState(2) s.setState(3)
场景
1、网页事件绑定
所有的事件监听都是观察者模式,所有事件写好(订阅)之后,等待被执行
image.png
2、Promise中的then
在then里面写好逻辑之后,不会马上触发,等待Promise的状态发生改变时触发
image.png
image.png
3、jquery中的callbacks
这是个底层的api,比如使用ajax时,就会到这个api里面执行
image.png
4、node.js自定义事件
event
// 基础api的引用const EventEmitter = require('events').EventEmitterconst emitter1 = new EventEmitter() emitter1.on('some', info => { // 监听 some 事件 console.log('f1',info) }) emitter1.on('some', info => { // 监听 some 事件 console.log('f2',info) })// 触发 some 事件emitter1.emit('some','XXX')// +++++++++++++++++++++++++++++++++++const emitter = new EventEmitter() emitter.on('sbowName', name => { console.log('event occured ', name) })// emit 时候可以传递参数过去emitter.emit('sbowName', 'zhangsan') // +++++++++++++++++++++++++++++++++++// 继承// 任何构造函数都可以继承 EventEmitter 的方法 on emitclass Dog extends EventEmitter { constructor(name) { super() this.name = name } }var simon = new Dog('simon')// 因为 simon 继承了 Dog ,所以便有他的 on 方法simon.on('bark', function () { console.log(this.name, ' barked') }) setInterval(() => { // 每一秒触发bark,都回去执行 console.log(this.name, ' barked') simon.emit('bark') }, 500)
stream
// stream 用到自定义事件const fs = require('fs')// 读取文件的 Streamconst readStream = fs.createReadStream('./data/file1.txt') // 此方法是看文件有多少字符let length = 0// 监听这个流// 将数据处理的函数和结束的函数定义好,直接去执行流,等待触发即可readStream.on('data', function (chunk) { // chunk读一点,吐出一点 length += chunk.toString().length }) readStream.on('end', function () { console.log(length) })
readline
var fs = require('fs')var readline = require('readline');// 查看有多少行var rl = readline.createInterface({ input: fs.createReadStream('./data/file1.txt') });var lineNum = 0// 这里是监听一行一行的数据rl.on('line', function(line){ lineNum++ }); rl.on('close', function() { console.log('lineNum', lineNum) });
处理http请求
var http = require('http')function serverCallback(req, res) { var method = req.method.toLowerCase() // 获取请求的方法 if (method === 'get') { } if (method === 'post') { // 接收 post 请求的内容 var data = '' req.on('data', function (chunk) { // “一点一点”接收内容 console.log('chunk', chunk.toString()) data += chunk.toString() }) req.on('end', function () { // 接收完毕,将内容输出 console.log('end') res.writeHead(200, {'Content-type': 'text/html'}) res.write(data) res.end() }) } } http.createServer(serverCallback).listen(8081) // 注意端口别和其他 server 的冲突console.log('监听 8081 端口……')
vue和react组件生命周期触发
组件其实都是构造函数,生成一个组件相当于构造函数初始化实例vue watch
image.png
设计原则验证
主题和观察者分离,不是主动触发而是被动监听,两者解耦
符合开放封闭原则
迭代器模式
介绍
1、顺序访问一个集合(有序列表,数组,对象是个无序列表)
2、使用者无需知道集合的内部结构(封装,目的在于生成一个访问机制,不需要外界知道内部结构)
示例
三个不同的数据结构遍历的方法有三种
image.png
统一的遍历方式
image.png
代码演示
class Iterator { constructor(conatiner) { this.list = conatiner.list this.index = 0 } next() { if (this.hasNext()) { // 如果还有下一项,直接返回当前这一项的index++ return this.list[this.index++] } // 如果没有,则返回null return null } hasNext() { // 判断有没有下一项 // this.index >= this.list.length 这句话的意思是有没有到头 if (this.index >= this.list.length) { return false } // 如果没有就是还有下一项 return true } }class Container { constructor(list) { this.list = list } // 生成遍历器 getIterator() { // 遍历器是有依据的,所以要传递一个参数 return new Iterator(this) } }// 测试代码let container = new Container([1, 2, 3, 4, 5])// 生成一个遍历器,通过这个遍历器可以兼容所有的有序结集合的数据结构let iterator = container.getIterator()while(iterator.hasNext()) { console.log(iterator.next()) }
ES6 Iterator
ES6 Iterator为何存在
image.png
ES6 Iterator是什么
image.png
示例
// 传入的data可以是任意的function each(data) { // 生成遍历器,类似jquery生成的遍历器 let iterator = data[Symbol.iterator]() // 有数据时返回 {value: 1, done: false} // console.log(iterator.next()) // console.log(iterator.next()) // console.log(iterator.next()) // console.log(iterator.next()) // 没有值的时候返回undefined,done 等于 true 就结束了 // console.log(iterator.next()) // 没有数据时返回 {value: undefined, done: true} // 因为不知道所遍历数据的长度length,所以使用while循环 let item = { done: false } while (!item.done) { // 每次获取next item = iterator.next() // 判断done是否结束 if (!item.done) { console.log(item.value) } } }// 测试let arr = [1, 2, 3, 4]let nodeList = document.getElementsByTagName('p')// 如果是对象,可以利用Map.setlet m = new Map() m.set('a', 100) m.set('b', 200) each(arr) each(nodeList) each(m)
image.png
设计原则验证
迭代器对象和目标对象分离
迭代器将使用者与目标对象隔离开
符合开放封闭原则
状态模式
介绍
1、一个对象有状态变化
2、每次状态变化都会触发一个逻辑
3、不能总是用if...else来控制
js代码
// 把状态抽象出来// 状态(红绿灯)class State { constructor(color) { this.color = color } handle(context) { console.log(`turn to ${this.color} light`) // 设置状态 context.setState(this) } }// 主体 实例class Context { constructor() { this.state = null } // 获取状态 getState() { return this.state } setState(state) { this.state = state } }// 测试代码let context = new Context()let greed = new State('greed')let yellow = new State('yellow')let red = new State('red')// 绿灯亮了greed.handle(context)console.log(context.getState())// 黄灯亮了yellow.handle(context)console.log(context.getState())// 红灯亮了red.handle(context)console.log(context.getState())
场景
1、有限状态机
有限个状态、以及在这些状态之间的变化
第三方库,npm i javascript-state-machine --save
<!DOCTYPE html><html><head> <meta charset="UTF-8"> <title>Document</title></head><body> <p>有限状态机</p> <button id="btn"></button> <script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script> <script src="./03-javascript-state-machine.js"></script> <script> // 状态机模型 var fsm = new StateMachine({ init: '收藏', // 初始状态,待收藏 transitions: [ { name: 'doStore', from: '收藏', to: '取消收藏' }, { name: 'deleteStore', from: '取消收藏', to: '收藏' } ], methods: { // 监听执行收藏 onDoStore: function () { alert('收藏成功') // 可以post请求 updateText() }, // 取消收藏 onDeleteStore: function () { alert('已取消收藏') updateText() } } }) var $btn = $('#btn') // 点击事件 $btn.click(function () { if (fsm.is('收藏')) { fsm.doStore(1) } else { fsm.deleteStore() } }) // 更新按钮文案 function updateText() { $btn.text(fsm.state) } // 初始化文案 updateText() </script></body></html>
作者:jia林
链接:https://www.jianshu.com/p/4b110e4c3bcd