《JavaScript 模式》知識點小抄本(下)

介紹

最近開始給本身每週訂個學習任務,學習結果反饋爲一篇文章的輸出,作好學習記錄。
這一週(02.25-03.03)我定的目標是《JavaScript 模式》的第七章學習一遍,學習結果的反饋就是本篇文章啦。
因爲內容實在太長,我將本文分爲兩部分:javascript

本文內容中主要參考《JavaScript 模式》,其中也有些案例是來自網上資料,有備註出處啦,如形成不便,請聯繫我刪改。 前端

過兩天我會把這篇文章收錄到我整理的知識庫 【Cute-JavaScript】 中,並已經同步到 【github】上面。java

六.外觀模式(Facade Pattern)

1.概念介紹

外觀模式(Facade Pattern)是一種簡單又常見的模式,它爲一些複雜的子系統接口提供一個更高級的統一接口,方便對這些子系統的接口訪問。 node

它不只簡化類中的接口,還對接口和調用者進行解耦,外觀模式也常被認爲是開發者必備,它能夠將一些複雜操做封裝起來,並建立一個簡單接口用於調用。git

2.優缺點和應用場景

2.1優勢

  • 輕量級,減小系統相互依賴。
  • 提升靈活性。
  • 提升了安全性。

2.2缺點

  • 不符合開閉原則,若是要改東西很麻煩,繼承重寫都不合適。

2.3應用場景

  • 爲複雜的模塊或子系統提供外界訪問的模塊。
  • 子系統相對獨立。
  • 預防低水平人員帶來的風險。
  • 項目重構。

3.基本案例

常常咱們在處理一些特殊狀況的時候,須要一塊兒調用好幾個方法,咱們使用外觀模式,就能夠將多個方法包裝成一個方法,哪裏須要使用直接調用這個包裝好的方法就能夠。
好比咱們常常處理瀏覽器事件,須要同時調用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;
        }
    }
};

7、代理模式(Proxy Pattern)

1.概念介紹

代理模式(Proxy Pattern) 爲其餘對象提供一種代理,來控制這個對象的訪問,代理是在客戶端和真實對象之間的介質。 設計模式

裝飾者模式1

簡單的理解:如咱們須要請明星來作廣告,咱們會先經過聯繫Ta的經紀人,談好條件纔會給明星籤合同。瀏覽器

2.優缺點和應用場景

2.1優勢

  • 職責單一且清晰。
  • 保護真實對象。
  • 開閉原則,高拓展性。

2.2缺點

  • 因爲在客戶端和真實對象間添加代理對象,致使請求處理速度變慢。
  • 實現代理模式須要額外工做,有些代理模式實現起來很是複雜。

2.3應用場景

  • 須要隱藏或保護某個類,則爲這個類添加代理。
  • 須要給不一樣訪問者提供不一樣權限,則在代理類上作判斷。
  • 須要爲某個類添加功能,如添加日誌緩存等,咱們能夠在代理的類作添加,而無論去改原來封裝好的類。

3.基本案例

這裏咱們以吃午餐問題來學習代理模式。一般狀況下,咱們會有兩種方式解決午餐問題:「去餐廳吃」和「叫外賣」。
去餐廳吃的話,咱們就是本身過去吃飯了唄,若是是叫外賣,咱們就會經過外賣小哥來拿到午餐才能吃起來。緩存

  • 去餐廳吃(沒有使用代理模式)
// 定義午餐類 參數 菜名
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('青椒炒肉')); // 午餐吃青椒炒肉

而且外賣小哥還會幫咱們作一些其餘事,好比幫咱們帶瓶可樂,咱們改造brotherleo這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('青椒炒肉'));
// 順手買瓶可樂!
// 午餐吃青椒炒肉

4.保護代理

仍是借用 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('青椒炒肉'));
// 順手買瓶可樂!
// 午餐吃青椒炒肉

5.虛擬代理

虛擬代理能把一些開銷大的對象,延遲到真正須要的時候纔去建立和執行。
咱們這裏舉個圖片懶加載的例子:
這個案例參考自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');

6.緩存代理

緩存代理是將一些開銷大的運算結果提供暫存功能,當下次計算時,參數和以前一直,則將緩存的結果返回:
這個案例參考自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);
    }
})();

8、中介者模式(Mediator Pattern)

1.概念介紹

中介者模式(Mediator Pattern) 是用來下降多個對象和類之間的通訊複雜性,促進造成鬆耦合,提升可維護性。

裝飾者模式1

在這種模式下,獨立的對象之間不能直接通訊,而是須要中間對象(mediator對象),當其中一個對象(colleague對象)狀態改變後,它會通知mediator對象,
而後mediator對象會把該變換通知到任意須要知道此變化的colleague對象。

2.優缺點和應用場景

2.1優勢

  • 下降類的複雜度,從一對多轉成一對一。
  • 爲各個類之間解耦。
  • 提升代碼可維護性。

2.2缺點

中介者會愈來愈龐大,變得難以維護。

2.3應用場景

  • 系統中對象之間存在比較複雜的引用關係,並且難以複用該對象。
  • 須要生成最少的子類,實現一箇中間類封裝多個類中的行爲的時候。

另外: 不要在職責混亂的時候使用。

3.基本案例

這裏咱們實現一個簡單的案例,一場測試結束後,公佈結果,告知解答出題目的人挑戰成功,不然挑戰失敗:
這個案例來自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 選手挑戰失敗;

4.書本案例

這個案例來自 《JavaScript 模式》第七章 中介者模式 的案例。
這裏咱們有這麼一個遊戲例子,規則是兩個玩家在規定時間內,比比誰點擊按鈕次數更多,玩家1按按鍵2,玩家2按按鍵0,而且計分板實時更新。

裝飾者模式1

這裏的中介者須要知道全部其餘對象信息,而且它須要知道哪一個玩家點擊了一次,隨後通知玩家。玩家進行遊戲的時候,還要通知中介者它作的事情,中介者更新分數並顯示比分。

這裏的player對象都是經過Player()構造函數生成,而且都有pointsname屬性,每次調用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)

9、觀察者模式(Observer Patterns)

1.概念介紹

觀察者模式(Observer Patterns) 也稱訂閱/發佈(subscriber/publisher)模式,這種模式下,一個對象訂閱定一個對象的特定活動,並在狀態改變後得到通知。
這裏的訂閱者稱爲觀察者,而被觀察者稱爲發佈者,當一個事件發生,發佈者會發布通知全部訂閱者,並經常以事件對象形式傳遞消息。

全部瀏覽器事件(鼠標懸停,按鍵等事件)都是該模式的例子。

咱們還能夠這麼理解:這就跟咱們訂閱微信公衆號同樣,當公衆號(發佈者)羣發一條圖文消息給全部粉絲(觀察者),而後全部粉絲都會接受到這篇圖文消息(事件),這篇圖文消息的內容是發佈者自定義的(自定義事件),粉絲閱讀後可能就會買買買(執行事件)。

2.觀察者模式 VS 發佈訂閱模式

2.1觀察者模式

一種一對多的依賴關係,多個觀察者對象同時監聽一個主題對象。這個主題對象在狀態上發生變化時,會通知全部觀察者對象,使它們可以自動更新本身。

觀察者模式

2.2發佈訂閱模式

發佈訂閱模式理念和觀察者模式相同,可是處理方式上不一樣。
在發佈訂閱模式中,發佈者和訂閱者不知道對方的存在,他們經過調度中心串聯起來。
訂閱者把本身想訂閱的事件註冊到調度中心,當該事件觸發時候,發佈者發佈該事件到調度中心(並攜帶上下文),由調度中心統一調度訂閱者註冊到調度中心的處理代碼。

裝飾者模式1

2.3二者異同點

  • 觀察者模式中,觀察者知道發佈者是誰,併發布者保持對觀察者進行記錄。而發佈訂閱模式中,發佈者和訂閱者不知道對方的存在。它們只是經過調度中心進行通訊。
  • 發佈訂閱模式中,組件是鬆散耦合的,正好和觀察者模式相反。
  • 觀察者模式大可能是同步,如當事件觸發,發佈者就會去調用觀察者的方法。而發佈訂閱模式大可能是異步的(使用消息隊列)。
  • 觀察者模式須要在單個應用程序地址空間中實現,而發佈-訂閱更像交叉應用模式。

儘管存在差別,但也有人說發佈-訂閱模式是觀察者模式的變異,由於它們概念上類似。

2.4二者優缺點

相同優勢:

  • 均可以一對多
  • 程序便於擴展

不一樣優勢:

  • 觀察者模式:單向解耦,發佈者不須要清楚訂閱者什麼時候何地訂閱,只須要維護訂閱隊列,發送消息便可
  • 發佈訂閱模式:雙向解耦,發佈者和訂閱者都不用清楚對方,所有由訂閱中心作處理

缺點:

  • 若是一個被觀察者和多個觀察者的話,會增長維護的難度,而且會消耗不少時間。
  • 若是觀察者和發佈者之間有循環依賴,可能會致使循環調用引發系統奔潰。
  • 觀察者沒法得知觀察的目標對象是如何發生變化,只能知道目標對象發生了變化。
  • 發佈訂閱模式,中心任務太重,一旦崩潰,全部訂閱者都會受到影響。

4.基本案例

咱們日常一直使用的給DOM節點綁定事件,也是觀察者模式的案例:

document.body.addEventListener('click', function(){
    alert('ok');
},false);
document.body.click();

這裏咱們訂閱了document.bodyclick事件,當body點擊它就向訂閱者發送消息,就會彈框ok。咱們也能夠添加不少的訂閱。

4.觀察者模式 案例

本案例來自 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')

5.發佈訂閱模式 案例

本案例來自 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)

參考資料

  1. 《JavaScript Patterns》
Author 王平安
E-mail pingan8787@qq.com
博 客 www.pingan8787.com
微 信 pingan8787
每日文章推薦 https://github.com/pingan8787...
JS小冊 js.pingan8787.com
微信公衆號 前端自習課

前端自習課

相關文章
相關標籤/搜索