最近開始給本身每週訂個學習任務,學習結果反饋爲一篇文章的輸出,作好學習記錄。
這一週(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 |
微信公衆號 | 前端自習課 |