-
设计模式
-
单例模式
-
定义:1、只有一个实例 2、可以全局的访问
-
主要解决:一个全局使用的类,频繁的创建和销毁
-
何时使用:当你想控制实例的数目,节省系统化资源的时候
-
如何实现:判断系统是否已经有这个单例,如果有则返回,没有则创建
-
单例模式有点:内存中只要一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如首页页面的缓存)
-
使用场景:1、全局的缓存 2、弹窗
-
例子:登录的弹窗
-
1、加载完成的时候,已经创建好这个弹窗了,一开始是隐藏的状态,弹窗出现
缺点:资源的浪费
var loginLayer = (function () { var div = document.createElement('div') div.innerHTML = '我是登录的弹窗' div.style.display = 'none' document.body.appendChild(div) return div })() document.getElementById('button').onClick = function() { loginLayer.style.display = 'block' }
-
2、点击的时候创建
缺点:频繁的创建和销毁
var ceateLoginLayer = function () { var div = document.createElement('div') div.innerHTML = '我是登录的弹窗' div.style.display = 'none' document.body.appendChild(div) return div } document.getElementById('button').onClick = function() { var loginLayer = ceateLoginLayer() loginLayer.style.display = 'block' }
-
3、单例模式
- 需要一个标记——闭包
var ceateLoginLayer = (function() { var div // 如果说这个div不被销毁,一直在内存中的话 return function() { if(!div) { div = document.createElement('div') div.innerHTML = '我是登录的弹窗' div.style.display = 'none' document.body.appendChild(div) } // 第二次和第一次,都需要return div return div } }) document.getElementById('button').onClick = function() { // 不能在这里ceateLoginLayer()(),会创建多个div var loginLayer = ceateLoginLayer() loginLayer.style.display = 'block' }
-
4、改进:单一职责
- 抽取形成单例,核心代码——闭包
var getSingle = function (fn) { var result return function () { if (!result) { result = fn.apply(this, arguments) } return result } } var createLogin = function () { var div = document.createElement('div') div.innerHTML = '我是登录的弹窗' div.style.display = 'none' document.body.appendChild(div) return div } var createSingleLogin = getSingle(createLogin) document.getElementById('button').onclick = function () { var loginLayer = createSingleLogin() loginLayer.style.display = 'block' }
-
5、es6实现单例模式,不需要使用闭包
-
es5构造函数
function Person(name, sex){ this.name = name this.sex = sex } Person.prototype.say = function() { console.log('xxx') } let person1 = new Person('xx', '女')
-
es6类
class Person{ constructor(name, sex){ this.name = name this.sex = sex // 实际开发中 更多的是做初始化操作 } say() { console.log('xxx') } } let person1 = new Person('xxx', 'man') // new的时候会自动调用constructor方法 console.log(person1.say === Person.prototype.say) // true
- static静态方法只能被类本身调用,而不能被实例调用
class Foo { static classMethod() { return 'hello' } } let foo = new Foo() foo.classMethod() // 报错 Foo.classMethod() // 可以调用
-
es6静态方法实现单例模式
- static静态方法只能被类本身调用,而不能被实例调用
class singleMode { constructor(name, creator, product) { this.name = name this.creator = creator this.product = product } static getInstance(name, creator, product) { if(!this.instance) { this.instance = new singleMode(name, creator, product) } return this.instence } } let singleMode1 = singleMode.getInstance('xxx', '111', 'sss') let singleMode2 = singleMode.getInstance('xxx2', '1112', 'sss2') console.log(singleMode1 === singleMode2) // true, 返回的都是第一次创建的类
-
-
-
-
策略模式
-
定义一系列的算法,把它们封装起来,并且它们之间可以互相替换
-
可信:将算法的使用和算法的实现分离开来
-
例子:年底奖金发放
-
绩效S:4倍工资;绩效A:3倍工资;绩效B:2倍工资
-
1、缺点:if语句过多,代码过于庞杂,缺发弹性
var calculate = function(level, salary) { if (level == 'S') { return salary * 4 } if (level == 'A') { return salary * 3 } if (level == 'B') { return salary * 2 } }
-
2、策略模式
// 适用于所有语言 var pS = function(){} pS.prototype.calculate = function(salary) { return salary * 4 } var pA = function(){} pA.prototype.calculate = function(salary) { return salary * 3 } var pB = function(){} pB.prototype.calculate = function(salary) { return salary * 2 } var Bouns = function() { this.salary = null this.strategy = null } Bouns.prototype.setSalary = function(salary){ this.salary= salary } Bouns.prototype.setStrategy= function(strategy){ this.strategy = strategy } Bouns.prototype.getBouns = function() { return this.strategy.calculate(this.salary) } var bonus = new Bouns() bonus.setSalary(10000) bonus.setStrategy(new pS()) console.log(bonus.getBouns())
// 改写 js的语言特性 var strategies = { "S": function(salary) { return salary * 4 }, "A": function(salary) { return salary * 3 }, "B": function(salary) { return salary * 2 } } var getBouns = function(level, salary) { return strategies[level](salary) }
-
-
-
例子:表单验证的封装
-
一般实现
<form action='xxx.com' method='post' id='registerForm'> 请输入用户名:<input type='text' name='username'> 请输入密码:<input type='password' name='password'> 请输入电话号码:<input type='text' name='phonenumer'> <button>提交</button> </form> var registerForm = document.getElementById('registerForm') registerForm.onsubmit = function() { if(registerForm.username.value === '') { alert('用户名不能为空') return false } if(registerForm.password.value < 6) { alert('密码不能小于6位') return false } if(!/^1[3|5|8][0-9]{9}$/.test(registerForm.phonenumer.value)) { alert('手机号格式不正确') return false } }
-
策略模式封装
// 策略对象:一系列的算法——业务逻辑 var strategies = { isNoneEmpty: function(value, errorMsg) { if (value === '') { return errorMsg } }, minLength: function(value, length, errorMsg) { if (value.length < length) { return errorMsg } }, isMobie: function(value, errorMsg) { if (!\^1[3|5|8][0-9]{9}\.test(value)) { return errorMsg } } } var registorForm = document.getElementById('registorForm') // 假设有一个验证类Validator, new Validator() var validateFun = function() { var validator = new Validator() // 添加验证规则 validator.add(registorForm.username, 'isNoneEmpty', '用户名不能为空') validator.add(registorForm.password, 'minLength:6', '密码不能小于6位') validator.add(registorForm.phonenumer, 'isMobie', '手机号格式不正确') // 开启验证 var errorMsg = validator.start() return errorMsg } registorForm.onsubmit = function() { var errorMsg = validateFun() if(errorMsg) { alert(errorMsg) return false } } // 封装策略类 var Validator = function() { // 保存验证规则的数组 this.cache = [] } Validator.prototype.add = function(dom, rule, errorMsg) { var arg = rule.split(':') this.cache.push(function() { var strategy = arg.shift() // 用户选择的鹅验证规则 arg.unshift(dom.value) arg.push(errorMsg) // 注意:这里已经执行函数了 return strategies[strategy].apply(dom, arg) // 或者return strategies[strategy](...arg ) }) } Validator.prototype.start = function(){ for(var i = 0, vaFunc; vaFunc = this.cache[i++];){ var msg = vaFunc() // 正常需要提示一个之后需要终端循环 if(msg) { return msg } } }
-
-
-
发布订阅模式
-
发布订阅模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知(先订阅再发布)
-
作用
- 支持简单的广播通信:当对象状态发生改变时,会自动通知已经订阅过的对象
- 可以应用在异步编程中,替代回调函数:可以订阅ajax之后的事件,只需要订阅自己需要的部分(那么ajax调用发布之后订阅的就可以拿到消息了)(不需要关心对象在异步运行时候的状态)
- 对象之间的松耦合:两个对象之间都互相不了解彼此,但是不影响通信,当有新的订阅者出现的时候,发布的代码无需要改变,同样发布的代码改变,只要之前约定的事件的名称没有改变,也不影响订阅
- vue、react之间实现跨组件之间的传值
-
例子
-
小红想买一双鞋子,卖光了,联系卖家有货的时候联系他,卖家说要一个星期才有货,可以先收藏店铺,有货的时候通知他,小红收藏了店铺;与此同时,小明,小花等也收藏了店铺,等来货时就会一次通知他们
-
如何实现
- 谁是发布者(比如卖家)
- 给发布者添加一个缓存列表,用于存放回调函数来通知订阅者(比如买家收藏了卖家的店铺,就会有一个收藏了该店铺的名单列表)
- 发布消息,发布者遍历这个缓存列表,依次触发里面存放的订阅者回调函数
var shopObj = {} // 定义发布者 shopObj.list = [] // 缓存列表,存放的是订阅的函数 // 增加订阅者 shopObj.listen = function(fn) { shopObj.list.push(fn) } // 发布消息 shopObj.trigger = function() { for(var i =0, fn; fn = this.list[i++];) { fn.apply(this, arguments) } } // 先订阅,再发布 shopObj.listen(function(color, size){ console.log(color) console.log(size) }) shopObj.listen(function(color, size){ console.log('再次', color) console.log('再次', size) }) shopObj.trigger('red', 42) shopObj.trigger('black', 43) // 上面的写法会造成,只要listen,就会循环多次 // 改写 var shopObj = {} // 定义发布者 shopObj.list = [] // 缓存列表,存放的是订阅的函数 // 增加订阅者 shopObj.listen = function(key, fn) { if(!this.list[key]){ this.list[key] = [] } this.list[key].push(fn) } // 发布消息 shopObj.trigger = function() { // arguments是类数组对象,并没有shift方法 var key = Array.prototype.shift.call(arguments) var fns = this.list[key] if (!fns || fns.length === 0) { return } for(var i =0, fn; fn = fns[i++];) { fn.apply(this, arguments) // fn(...arguments) } } // 上面的无法取消订阅 // 封装事件 var event = { list: [], listen: function(key, fn) { if(!this.list[key]){ this.list[key] = [] } this.list[key].push(fn) }, trigger: function() { // arguments是类数组对象,并没有shift方法 var key = Array.prototype.shift.call(arguments) var fns = this.list[key] if (!fns || fns.length === 0) { return } for(var i =0, fn; fn = fns[i++];) { fn.apply(this, arguments) // fn(...arguments) } } } // 取消订阅 event.remove = function(key, fn){ var fns = this.list[key] if(!fns) { return false } if (!fn){ fn && (fns.length = 0) } else { for(var i = fns.length -1; i >=0; i--){ var _fn = fns[i] if(_fn === fn) { fns.splice(i, 1) } } } } // 封装 var Event = (function(){ var list = [] var listen = function(key, fn) { if(!this.list[key]){ this.list[key] = [] } this.list[key].push(fn) } var trigger = function() { // arguments是类数组对象,并没有shift方法 var key = Array.prototype.shift.call(arguments) var fns = this.list[key] if (!fns || fns.length === 0) { return } for(var i =0, fn; fn = fns[i++];) { fn.apply(this, arguments) // fn(...arguments) } } var remove = function(key, fn){ var fns = this.list[key] if(!fns) { return false } if (!fn){ fn && (fns.length = 0) } else { for(var i = fns.length -1; i >=0; i--){ var _fn = fns[i] if(_fn === fn) { fns.splice(i, 1) } } } } return { list, listen, trigger, remove } })()
-
-
改进异步操作中的强耦合——例子
-
一个商城网站,网站有很多模块,模块渲染的公共前提是先用ajax异步请求获取用户的登录信息
login.succ(function(data){ header.setAvatar(data.avatar) nav.setAvatar(data.avatar) messgae.refresh() cart.refresh() }) // 缺点:强耦合 // header模块里设置头像的方法setAvatar、购物车刷新方法叫refresh,强耦合 // header模块不能随意改变setAvatar方法名,自身名称也不能被改为header1
-
发布订阅模式实现低耦合
$.ajax('xxx', function(data){ login.trigger('loginSucc', data) // 发布 }) // 订阅 var header = (function(){ login.listen('loginSucc', function(data){ header.setAvatar(data.avatar) }) return { setAvatar: function(dtata) { console.log('设置header头像') } } })() var nav = (function(){ login.listen('loginSucc', function(data){ nav.setAvatar(data.avatar) }) return { setAvatar: function(dtata) { console.log('设置nav头像') } } })()
-
-
-
vue react 实现跨组件之间的传值
// 父组件 <template> <div> <input type="text" v-modal="name" /> <button @click="doPub">发布</button> <Son /> </div> </template> <script> import Son from './Son' // 自己封装 import pubsub from '@/utils/pubsub.js' export default { name: 'HelloWorld', data: { return { name: '' } }, components: { Son }, methods: { // 发布 doPub(){ pubsub.trigger('item', this.name) } } } </script>
// 子组件Son <template> <div> <h1>{{h1}}</h1> </div> </template> <script> import pubsub from '@/utils/pubsub.js' export default { name: 'Son', mounted(){ // 订阅 pubsub.listen('item', data =>{ this.h1 = data }) } } </script>
// 孙子组件 <template> <div> <h1>{{h1}}</h1> </div> </template> <script> import pubsub from '@/utils/pubsub.js' export default { name: 'Children', mounted(){ // 订阅 pubsub.listen('item', data =>{ this.h1 = data }) } } </script>
// 改写Son组件 <template> <div> <Children /> </div> </template> <script> import Children from './Children' import pubsub from '@/utils/pubsub.js' export default { name: 'Son', // 订阅还是在最上层的HelloWorld组件里 components: { Children } } </script>
// 封装 var Event = (function(){ var list = [] var listen = function(key, fn) { if(!list[key]){ list[key] = [] } list[key].push(fn) } var trigger = function() { // arguments是类数组对象,并没有shift方法 var key = Array.prototype.shift.call(arguments) var fns = list[key] if (!fns || fns.length === 0) { return } for(var i =0, fn; fn = fns[i++];) { fn.apply(this, arguments) // fn(...arguments) } } var remove = function(key, fn){ var fns = list[key] if(!fns) { return false } if (!fn){ fn && (fns.length = 0) } else { for(var i = fns.length -1; i >=0; i--){ var _fn = fns[i] if(_fn === fn) { fns.splice(i, 1) } } } } return { list, listen, trigger, remove } })() export default Event
-
PubSubJs插件 npm install pubsub-js —save-dev
-
-