最近开始给本身每周订个学习任务,学习结果反馈为一篇文章的输出,作好学习记录。
这一周(02.25-03.03)我定的目标是《JavaScript 模式》的第七章学习一遍,学习结果的反馈就是本篇文章啦。
因为内容实在太长,我将本文分为两部分:javascript
本文内容中主要参考《JavaScript 模式》,其中也有些案例是来自网上资料,有备注出处啦,如形成不便,请联系我删改。 前端
过两天我会把这篇文章收录到我整理的知识库 【Cute-JavaScript】 中,并已经同步到 【github】上面。java
外观模式(Facade Pattern)是一种简单又常见的模式,它为一些复杂的子系统接口提供一个更高级的统一接口,方便对这些子系统的接口访问。 node
它不只简化类中的接口,还对接口和调用者进行解耦,外观模式也常被认为是开发者必备,它能够将一些复杂操做封装起来,并建立一个简单接口用于调用。git
常常咱们在处理一些特殊状况的时候,须要一块儿调用好几个方法,咱们使用外观模式,就能够将多个方法包装成一个方法,哪里须要使用直接调用这个包装好的方法就能够。
好比咱们常常处理浏览器事件,须要同时调用stopPropagation()
和preventDefault()
,因而咱们就能够新建一个外观方法,实现这两个方法同时调用:github
let myEvent = { // ... stop: e => { e.stopPropagation(); e.preventDefault(); } };
而后咱们也可使用外观模式,来作IE事件的兼容性:segmentfault
let myEvent = { // ... stop: e => { // 其余 if(typeof e.preventDefault === 'function'){ e.preventDefault(); } if(typeof e.stopPropagation === 'function'){ e.stopPropagation(); } // IE if(typeof e.returnValue === 'boolean'){ e.returnValue = false; } if(typeof e.cancelBubble === 'boolean'){ e.cancelBubble = true; } } };
代理模式(Proxy Pattern) 为其余对象提供一种代理,来控制这个对象的访问,代理是在客户端和真实对象之间的介质。 设计模式
简单的理解:如咱们须要请明星来作广告,咱们会先经过联系Ta的经纪人,谈好条件才会给明星签合同。浏览器
这里咱们以吃午餐问题来学习代理模式。一般状况下,咱们会有两种方式解决午餐问题:“去餐厅吃”和“叫外卖”。
去餐厅吃的话,咱们就是本身过去吃饭了呗,若是是叫外卖,咱们就会经过外卖小哥来拿到午餐才能吃起来。缓存
// 定义午餐类 参数 菜名 let Lunch = function(greens){ this.greens = greens; } Lunch.prototype.getGreens = function(){ return this.greens; } // 定义我这个对象 let leo = { buy: function(greens){ console.log(`午餐吃${greens.getGreens()}`); } } // 去餐厅吃 leo.buy(new Lunch('青椒炒肉')); // 午餐吃青椒炒肉
// 定义午餐类 参数 菜名 let Lunch = function(greens){ this.greens = greens; } Lunch.prototype.getGreens = function(){ return this.greens; } // 定义外卖小哥这个对象 let brother = { buy: function(lunch){ leo.buy(lunch.getGreens()); } } // 定义我这个对象 let leo = { buy: function(greens){ console.log(`午餐吃${greens}`); } } // 叫外卖 brother.buy(new Lunch('青椒炒肉')); // 午餐吃青椒炒肉
而且外卖小哥还会帮咱们作一些其余事,好比帮咱们带瓶可乐,咱们改造brother
和leo
这2个对象,再看看效果:
let brother = { buy: function(lunch){ if(leo.needCola) leo.buyCola(); leo.buy(lunch.getGreens()); } } let leo = { needCola: true, buy: function(greens){ console.log(`午餐吃${greens}`); }, buyCola: function(){ console.log(`顺手买瓶可乐!`); } } brother.buy(new Lunch('青椒炒肉')); // 顺手买瓶可乐! // 午餐吃青椒炒肉
仍是借用 3.基本案例 的叫外卖的例子,咱们如今要实现保护代理,而咱们须要外卖小哥为了咱们的身体健康,超过晚上9点,就不帮咱们买可乐。
仍是改造上面买可乐的brother
对象代码:
let brother = { buy: function(lunch){ let nowDate = new Date(); if(nowDate.getHours() >= 21){ console.log('亲,这么晚不要喝可乐哟!'); }else{ if(leo.needCola) leo.buyCola(); leo.buy(lunch.getGreens()); } } } brother.buy(new Lunch('青椒炒肉')); // 顺手买瓶可乐! // 午餐吃青椒炒肉
虚拟代理能把一些开销大的对象,延迟到真正须要的时候才去建立和执行。
咱们这里举个图片懒加载的例子:
这个案例参考自JS设计模式-代理模式.
// 图片加载 let ele = (function(){ let node = document.createElement('img'); document.body.appendChild(node); return{ setSrc : function(src){ node.src = src; } } })() // 代理对象 let proxy = (function(){ let img = new Image(); img.onload = function(){ ele.setSrc(this.src); } return { setSrc : function(src){ img.src = src; ele.setSrc('loading.png'); } } })() proxy.setSrc('example.png');
缓存代理是将一些开销大的运算结果提供暂存功能,当下次计算时,参数和以前一直,则将缓存的结果返回:
这个案例参考自JS设计模式-代理模式.
//计算乘积 let mult = function(){ let result = 1; for(let i = 0; i<arguments.length; i++){ result *= arguments[i]; } return result; } // 缓存代理 let proxy = (function(){ let cache = {}; return function(){ let args = Array.prototype.join.call(arguments, '',); if(args in cache){ return cache[args]; } return cache[args] = mult.apply(this,arguments); } })();
中介者模式(Mediator Pattern) 是用来下降多个对象和类之间的通讯复杂性,促进造成松耦合,提升可维护性。
在这种模式下,独立的对象之间不能直接通讯,而是须要中间对象(mediator
对象),当其中一个对象(colleague
对象)状态改变后,它会通知mediator
对象,
而后mediator
对象会把该变换通知到任意须要知道此变化的colleague
对象。
中介者会愈来愈庞大,变得难以维护。
另外: 不要在职责混乱的时候使用。
这里咱们实现一个简单的案例,一场测试结束后,公布结果,告知解答出题目的人挑战成功,不然挑战失败:
这个案例来自JavaScript 中常见设计模式整理
const player = function(name) { this.name = name; playerMiddle.add(name); } player.prototype.win = function() { playerMiddle.win(this.name); } player.prototype.lose = function() { playerMiddle.lose(this.name); } const playerMiddle = (function() { // 将就用下这个 demo,这个函数当成中介者 const players = []; const winArr = []; const loseArr = []; return { add: function(name) { players.push(name) }, win: function(name) { winArr.push(name) if (winArr.length + loseArr.length === players.length) { this.show() } }, lose: function(name) { loseArr.push(name) if (winArr.length + loseArr.length === players.length) { this.show() } }, show: function() { for (let winner of winArr) { console.log(winner + '挑战成功;') } for (let loser of loseArr) { console.log(loser + '挑战失败;') } }, } }()) const a = new player('A 选手'); const b = new player('B 选手'); const c = new player('C 选手'); a.win() b.win() c.lose() // A 选手挑战成功; // B 选手挑战成功; // C 选手挑战失败;
这个案例来自 《JavaScript 模式》第七章 中介者模式 的案例。
这里咱们有这么一个游戏例子,规则是两个玩家在规定时间内,比比谁点击按钮次数更多,玩家1按按键2,玩家2按按键0,而且计分板实时更新。
这里的中介者须要知道全部其余对象信息,而且它须要知道哪一个玩家点击了一次,随后通知玩家。玩家进行游戏的时候,还要通知中介者它作的事情,中介者更新分数并显示比分。
这里的player
对象都是经过Player()
构造函数生成,而且都有points
和name
属性,每次调用play()
都会增长1分并通知中介者。
function Player(name){ this.points = 0; this.name = name; } Player.prototype.play = function(){ this.points += 1; mediator.played(); }
计分板有个update()
方法,当玩家回合结束就会调用,它不知道任何玩家的信息也没有保存分值,只是实现展现当前分数。
let scoreboard = { // 待更新HTML元素 ele: document.getElementById('result'); // 更新比分 update: function (score){ let msg = ''; for(let k in score){ if(score.hasOwnProperty(k)){ msg = `<p>${k} : ${score[k]}<\/p>` } } this.ele.innerHTML = msg; } }
接下来建立mediator
对象:
let mediator = { players: {}, // 全部玩家 setup: function(){ // 初始化 let players = this.players; players.homw = new Player('Home'); players.guest = new Player('Guest'); }, // 当有人玩时 更新分数 played: function(){ let players = this.players let score = { Home: players.home.points, Guest: players.guest.points, } scoreboard.update(score); } // 处理用户交互 keypress: function(e){ e = e || window.event; // 兼容IE if(e.which === 49){ // 按键1 mediator.players.home.play(); } if(e.which === 48){ // 按键0 mediator.players.guest.play(); } } }
最后就是须要运行和卸载游戏了:
mediator.setup(); window.onkeypress = mediator.keypress; // 游戏30秒后结束 setTimeout(function(){ window.onkeypress = null; alert('游戏结束'); }, 30000)
观察者模式(Observer Patterns) 也称订阅/发布(subscriber/publisher)模式,这种模式下,一个对象订阅定一个对象的特定活动,并在状态改变后得到通知。
这里的订阅者称为观察者,而被观察者称为发布者,当一个事件发生,发布者会发布通知全部订阅者,并经常以事件对象形式传递消息。
全部浏览器事件(鼠标悬停,按键等事件)都是该模式的例子。
咱们还能够这么理解:这就跟咱们订阅微信公众号同样,当公众号(发布者)群发一条图文消息给全部粉丝(观察者),而后全部粉丝都会接受到这篇图文消息(事件),这篇图文消息的内容是发布者自定义的(自定义事件),粉丝阅读后可能就会买买买(执行事件)。
一种一对多的依赖关系,多个观察者对象同时监听一个主题对象。这个主题对象在状态上发生变化时,会通知全部观察者对象,使它们可以自动更新本身。
发布订阅模式理念和观察者模式相同,可是处理方式上不一样。
在发布订阅模式中,发布者和订阅者不知道对方的存在,他们经过调度中心串联起来。
订阅者把本身想订阅的事件注册到调度中心,当该事件触发时候,发布者发布该事件到调度中心(并携带上下文),由调度中心统一调度订阅者注册到调度中心的处理代码。
尽管存在差别,但也有人说发布-订阅模式是观察者模式的变异,由于它们概念上类似。
相同优势:
不一样优势:
缺点:
咱们日常一直使用的给DOM节点绑定事件,也是观察者模式的案例:
document.body.addEventListener('click', function(){ alert('ok'); },false); document.body.click();
这里咱们订阅了document.body
的click
事件,当body
点击它就向订阅者发送消息,就会弹框ok
。咱们也能够添加不少的订阅。
本案例来自 javascript 观察者模式和发布订阅模式。
class Dom { constructor() { // 订阅事件的观察者 this.events = {} } /** * 添加事件的观察者 * @param {String} event 订阅的事件 * @param {Function} callback 回调函数(观察者) */ addEventListener(event, callback) { if (!this.events[event]) { this.events[event] = [] } this.events[event].push(callback) } removeEventListener(event, callback) { if (!this.events[event]) { return } const callbackList = this.events[event] const index = callbackList.indexOf(callback) if (index > -1) { callbackList.splice(index, 1) } } /** * 触发事件 * @param {String} event */ fireEvent(event) { if (!this.events[event]) { return } this.events[event].forEach(callback => { callback() }) } } const handler = () => { console.log('fire click') } const dom = new Dom() dom.addEventListener('click', handler) dom.addEventListener('move', function() { console.log('fire click2') }) dom.fireEvent('click')
本案例来自 javascript 观察者模式和发布订阅模式。
class EventChannel { constructor() { // 主题 this.subjects = {} } hasSubject(subject) { return this.subjects[subject] ? true : false } /** * 订阅的主题 * @param {String} subject 主题 * @param {Function} callback 订阅者 */ on(subject, callback) { if (!this.hasSubject(subject)) { this.subjects[subject] = [] } this.subjects[subject].push(callback) } /** * 取消订阅 */ off(subject, callback) { if (!this.hasSubject(subject)) { return } const callbackList = this.subjects[subject] const index = callbackList.indexOf(callback) if (index > -1) { callbackList.splice(index, 1) } } /** * 发布主题 * @param {String} subject 主题 * @param {Argument} data 参数 */ emit(subject, ...data) { if (!this.hasSubject(subject)) { return } this.subjects[subject].forEach(callback => { callback(...data) }) } } const channel = new EventChannel() channel.on('update', function(data) { console.log(`update value: ${data}`) }) channel.emit('update', 123)
Author | 王平安 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推荐 | https://github.com/pingan8787... |
JS小册 | js.pingan8787.com |
微信公众号 | 前端自习课 |