JS設計模式使用場景

代理模式

  • 虛擬代理圖片預加載
    如果直接給某個 img 標簽節點設置 src 屬性,由于圖片過大或者網絡不佳,圖片的位置往往有段時間會是一片空白。常見的做法是先用一張loading 圖片占位,然后用異步的方式加載圖片,等圖片加載好了再把它填充到 img 節點里,這種場景就很適合使用虛擬代理。
var myImage = (function() {
    var imgNode = document.createElement('img');
    document.body.appendChild(imgNode);
    return {
        setSrc: function(src) {
            imgNode.src = src;
        }
    }
})();
var proxyImage = (function() {
    var img = new Image;
    img.onload = function() {
        //等真正圖片加載好了之后,再改變成真實圖片
        myImage.setSrc(this.src);
    }
    return {
        setSrc: function(src) {
            //先設置一張緩存圖片
            myImage.setSrc('img/loading.gif');
            img.src = src;
        }
    }
})();
proxyImage.setSrc('img/1.jpg');
  • 虛擬代理合并 HTTP 請求
    假設我們在做一個文件同步的功能,當我們選中一個 checkbox 的時候,它對應的文件就會被同
    步到另外一臺備用服務器上面。當我們選中 3 個 checkbox 的時候,依次往服務器發送了 3 次同步文件的請求。可以預見,如此頻繁的網絡請求將會帶來相當大的開銷。
    解決方案是,我們可以通過一個代理函數 proxySynchronousFile 來收集一段時間之內的請求,最后一次性發送給服務器。比如我們等待 2 秒之后才把這 2 秒之內需要同步的文件 ID 打包發給服務器,如果不是對實時性要求非常高的系統, 2 秒的延遲不會帶來太大副作用,卻能大大減輕服務器的壓力。代碼如下:
var synchronousFile = function(id) {
    console.log('開始同步文件, id 為: ' + id);
};
var proxySynchronousFile = (function() {
    var cache = [], // 保存一段時間內需要同步的 ID
        timer; // 定時器
    return function(id) {
        cache.push(id);
        if(timer) { // 保證不會覆蓋已經啟動的定時器
            return;
        }
        timer = setTimeout(function() {
            synchronousFile(cache.join(',')); // 2 秒后向本體發送需要同
            clearTimeout(timer); // 清空定時器
            timer = null;
            cache.length = 0; // 清空 ID 集合
        }, 2000);
    }
})();
var checkbox = document.getElementsByTagName('input');
for(var i = 0, c; c = checkbox[i++];) {
    c.onclick = function() {
        if(this.checked === true) {
            proxySynchronousFile(this.id);
        }
    }
};

責任鏈模式

一系列可能會處理請求的對象被連接成一條鏈,請求在這些對象之間依次傳遞,直到遇到一個可以處理它的對象。請求發送者只需要知道鏈中的第一個節點。ps:可以將業務代碼從if/else中釋放出來,便于程序的擴展。
業務例子:公司針對支付過定金的用戶有一定的優惠政策。在正式購買后,已經支付過 500 元定金的用戶會收到 100 元的商城優惠券, 200 元定金的用戶可以收到 50 元的優惠券,而之前沒有支付定金的用戶只能進入普通購買模式,也就是沒有優惠券,且在庫存有限的情況下不一定保證能買到。

/*職責鏈 */
/* orderType:表示訂單類型(定金用戶或者普通購買用戶), code 的值為 1 的時候是 500 元
                            定金用戶,為 2 的時候是 200 元定金用戶,為 3 的時候是普通購買用戶。
? pay:表示用戶是否已經支付定金,值為 true 或者 false, 雖然用戶已經下過 500 元定金的
                訂單,但如果他一直沒有支付定金,現在只能降級進入普通購買模式。
? stock:表示當前用于普通購買的手機庫存數量,已經支付過 500 元或者 200 元定金的用
戶不受此限制
*/
var order500 = function(orderType, pay, stock) {
    if(orderType === 1 && pay === true) {
        console.log('500 元定金預購,得到 100 優惠券');
    } else {
        return 'nextSuccessor'; // 我不知道下一個節點是誰,反正把請求往后面傳遞
    }
};
var order200 = function(orderType, pay, stock) {
    if(orderType === 2 && pay === true) {
        console.log('200 元定金預購,得到 50 優惠券');
    } else {
        return 'nextSuccessor'; // 我不知道下一個節點是誰,反正把請求往后面傳遞
    }
};
var orderNormal = function(orderType, pay, stock) {
    if(stock > 0) {
        console.log('普通購買,無優惠券');
    } else {
        console.log('手機庫存不足');
    }
};
var Chain = function(fn) {
    this.fn = fn;
    this.successor = null;
};
Chain.prototype.setNextSuccessor = function(successor) {
    return this.successor = successor;
};
Chain.prototype.passRequest = function() {
    var ret = this.fn.apply(this, arguments);
    if(ret === 'nextSuccessor') {
        return this.successor && this.successor.passRequest.apply(this.successor, arguments);
    }
    return ret;
};
var chainOrder500 = new Chain(order500);
var chainOrder200 = new Chain(order200);
var chainOrderNormal = new Chain(orderNormal);
//然后指定節點在職責鏈中的順序:
chainOrder500.setNextSuccessor( chainOrder200 );
chainOrder200.setNextSuccessor( chainOrderNormal );
chainOrder500.passRequest( 1, true, 500 ); // 輸出: 500 元定金預購,得到 100 優惠券
chainOrder500.passRequest( 2, true, 500 ); // 輸出: 200 元定金預購,得到 50 優惠券
chainOrder500.passRequest( 3, true, 500 ); // 輸出:普通購買,無優惠券
chainOrder500.passRequest( 1, false, 0 ); // 輸出:手機庫存不足

命令模式

  • 撤銷和重做
  • 播放和錄像功能
  • 宏命令

中介者模式

試想一下這個場景:一個手機購買界面,有選擇手機顏色,大小,型號等下拉框及輸入框。每當用戶進行操作后,要實時回顯用戶的操作,并且在某一項未選擇的時候,確認按鈕應該是被禁用狀態。
如果把這些內容都寫在操作的回調函數中,比如:在選擇手機顏色onselect事件中,寫很多邏輯代碼。那這樣就不利于程序的擴展。在以后新增了某個選項,則需要在每個輸入框的回調函數中增加對此新增的下拉框的判斷。
所以我們應該把邏輯代碼分離出來。交由中介者去完成判斷。對于新增的業務,也只需要改中介者這個對象中的業務代碼。在所有輸入框或下拉框的回調事件中,我們只需向中介者發個請求,告訴它,我現在變化了。中介者接收到這個信號后,會自動做一系列的處理判斷。代碼如下:

var goods = { // 手機庫存
    "red|32G": 3,
    "red|16G": 0,
    "blue|32G": 1,
    "blue|16G": 6
};
var mediator = (function() {
    var colorSelect = document.getElementById('colorSelect'),
        memorySelect = document.getElementById('memorySelect'),
        numberInput = document.getElementById('numberInput'),
        colorInfo = document.getElementById('colorInfo'),
        memoryInfo = document.getElementById('memoryInfo'),
        numberInfo = document.getElementById('numberInfo'),
        nextBtn = document.getElementById('nextBtn');
    return {
        changed: function(obj) {
            var color = colorSelect.value, // 顏色
                memory = memorySelect.value, // 內存
                number = numberInput.value, // 數量
                stock = goods[color + '|' + memory]; // 顏色和內存對應的手機庫存數量
            if(obj === colorSelect) { // 如果改變的是選擇顏色下拉框
                colorInfo.innerHTML = color;
            } else if(obj === memorySelect) {
                memoryInfo.innerHTML = memory;
            } else if(obj === numberInput) {
                numberInfo.innerHTML = number;
            }
            if(!color) {
                nextBtn.disabled = true;
                nextBtn.innerHTML = '請選擇手機顏色';
                return;
            }
            if(!memory) {
                nextBtn.disabled = true;
                nextBtn.innerHTML = '請選擇內存大小';
                return;
            }
            if(((number - 0) | 0) !== number - 0) { // 輸入購買數量是否為正整數
                nextBtn.disabled = true;
                nextBtn.innerHTML = '請輸入正確的購買數量';
                return;
            }
            nextBtn.disabled = false;
            nextBtn.innerHTML = '放入購物車';
        }
    }
})();
// 事件函數:
colorSelect.onchange = function() {
    mediator.changed(this);
};
memorySelect.onchange = function() {
    mediator.changed(this);
};
numberInput.oninput = function() {
    mediator.changed(this);
};

發布-訂閱模式

適用場景:一個網站,有好幾個模塊都需要獲取用戶信息后,進行渲染顯示。在未用此種設計模式之前,我們會想著在獲取用戶信息的ajax成功回調函數中,寫代碼,渲染各個模塊。但是這樣做會導致,我們新增一個模塊,需要去修改之前ajax的回調函數,加入新增部分的處理。而一個團隊中,可能ajax回調函數部分是同事A做的,而新增模塊是同事B負責的,其實B具體要做什么事情,A并不需要知道,A只需要告訴你,我已經拿到用戶信息了,你們想干嘛就干嘛吧。那么這時候A提供出一個接口供其他需要它的人來訂閱,在A完成任務后,告訴之前這些跟它打過招呼的模塊,我把這些對你們有用信息傳給你們,具體后續的事情由你們自己來決定。這就是發布-訂閱模式。

組合模式

這種模式非常適用于樹形結構

  • 掃描文件夾
/******************************* Folder ******************************/
var Folder = function (name) {
    this.name = name;
    this.files = [];
};
Folder.prototype.add = function (file) {
    this.files.push(file);
};
Folder.prototype.scan = function () {
    console.log('開始掃描文件夾: ' + this.name);
    for (var i = 0, file, files = this.files; file = files[i++];) {
        file.scan();
    }
};
/******************************* File ******************************/
var File = function (name) {
    this.name = name;
};
File.prototype.add = function () {
    throw new Error('文件下面不能再添加文件');
};
File.prototype.scan = function () {
    console.log('開始掃描文件: ' + this.name);
};
var folder = new Folder('學習資料');
var folder1 = new Folder('JavaScript');
var folder2 = new Folder('jQuery');
var file1 = new File('JavaScript 設計模式與開發實踐');
var file2 = new File('精通 jQuery');
var file3 = new File('重構與模式')
folder1.add(file1);
folder2.add(file2);
folder.add(folder1);
folder.add(folder2);
folder.add(file3);
var folder3 = new Folder('Nodejs');
var file4 = new File('深入淺出 Node.js');
folder3.add(file4);
var file5 = new File('JavaScript 語言精髓與編程實踐');
folder.add(folder3);
folder.add(file5);
folder.scan();

裝飾者模式

  • 裝飾方法
    當我們在擴展方法的時候,更保險的方法是將原來的方法保留下來,在新的方法中再執行一次原來的方法。比如我們想給 window 綁定 onload 事件,但是又不確定這個事件是不是已經被其他人綁定過,為了避免覆蓋掉之前的 window.onload 函數中的行為,我們一般都會先保存好原先的 window.onload,把它放入新的 window.onload 里執行:如:
window.onload = function() {
    alert(1);
}
var _onload = window.onload || function() {};
window.onload = function() {
    _onload();
    alert(2);
}

但是上面會存在一些問題。1、 例如丟失了this指向。2、 需維護_onload 這個中間變量,如果函數的裝飾鏈夠長,所需的中間變量越多。

  • 面向AOP編程,為方法添加前執行函數和后執行函數。
 Function.prototype.before = function(beforefn) {
    var __self = this; // 保存原函數的引用
    return function() { // 返回包含了原函數和新函數的"代理"函數
        beforefn.apply(this, arguments); // 執行新函數,且保證 this 不被劫持,新函數接受的參數
        // 也會被原封不動地傳入原函數,新函數在原函數之前執行
        return __self.apply(this, arguments); // 執行原函數并返回原函數的執行結果,
        // 并且保證 this 不被劫持
    }
}
Function.prototype.after = function(afterfn) {
    var __self = this;
    return function() {
        var ret = __self.apply(this, arguments);
        afterfn.apply(this, arguments);
        return ret;
    }
};

狀態模式

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容