javascript中的設計模式(一)

模式1 - 單例模式ajax

單例模式的核心是確保只有一個實例,而且提供全局訪問。算法

特色:設計模式

  1. 知足「單一職責原則」 : 使用代理模式,不在構造函數中判斷是否已經建立過該單例;數組

  2. 知足惰性原則瀏覽器

應用:
彈出登錄窗口。緩存

實例:閉包

var getSingle = function (fn) {
    var res;
    return function() {
        return res || (res = fn.apply(this, arguments));
    }
}

var createPopup() {
    var div = document.createElement('div');
    div.innerHTML = "Login window";
    div.style.display = "none"; 
    document.body.appendChild(div);
    return div;
}

var createLoginPopup = getSingle(createPopup);            //create popup div here by using a given function, 知足兩個原則 

document.getElementById("loginBt").onclick = function() {
    var popup = createLoginPopup();
    pop.style.display = "block";
}

模式2 - 策略模式app

定義一個個能夠相互替換的算法,而且把他們封裝起來。函數

特色:動畫

  1. 符合開放-封閉原則 : 要修改使用的算法時不用深刻函數內部進行修改,只需修改策略類;

  2. 將算法的實現與使用分離開,提升算法複用性;

  3. 經過組合、委託和多態避免多重條件選擇語句;

應用:
動畫實現不一樣的緩動效果。

通常分爲兩個部分:策略類於環境類。策略類用於封裝各類算法,而且負責具體的計算過程; 環境類負責接收用戶的請求,而且把請求委託給某一個策略類。由於各個策略類實現的算法和計算的結果不一樣,而環境類調用策略類的方法倒是相同的,這就體現了多態性。要想實現不一樣的算法,只須要替換環境類中的策略類便可。

在js中,咱們沒必要構造策略類,可直接使用函數做爲策略對象。

示例:

var strategies = {
    "s1": function() {
        //algo 1
    },
    "s2": function() {
        //algo 2
    }, 
    "s3": function() {
        //algo 3
    }
 }
 
 var someContext =  new SomeConext();
 someContext.start("s1");  //using s1 to calculate
 //someContext.add("s1");  or add s1 as a rule for validation

模式3 - 代理模式

代理就像一個經紀人,當用戶不方便直接訪問某個對象或者須要對訪問進行一些過濾/加工時,能夠經過代理來進行對象訪問。代理會對請求進行一些處理,而後再將請求傳遞給本體。

通常分爲保護代理和虛擬代理:

  1. 保護代理負責過濾掉一些請求;

  2. 虛擬代理則是將一些花銷比較大的操做延遲到真正他的時候再去建立,例如new一個對象。

特色:

  1. 保證對象符合單一職責原則;

應用:
圖片預加載, 合併http請求, 惰性加載, 緩存代理(避免重複計算,能夠寫一個通用的緩存對象(其實就是一個閉包),將高階函數做爲參數傳入)。


模式4 - 迭代器模式

顧名思義,迭代器能夠將對於一個聚合對象內部元素的訪問與業務邏輯分離開。

通常分爲內部迭代器和外部迭代器:

  1. 內部迭代器只需一次初始調用,不須要關心迭代器的內部實現;

  2. 外部迭代器須要顯式地請求下一個元素,所以能夠手工控制迭代過程和順序,例如調用iterator.next();

不管是哪一種迭代器,只要聚合對象有length屬性而且能夠經過下標訪問,那麼就能夠被迭代。所以類數組對象及字面量對象(用for in)均可以。

絕大部分語言都內置了迭代器。

應用:
能夠經過添加終止條件來中斷迭代:在callback函數中判斷,若是return值爲false,則經過break跳出迭代循環。
由此能夠設計根據瀏覽器類型建立的返回對象,按優先級一個個迭代。


模式5 - 訂閱發佈模式

將許多對象弱耦合起來,當一個對象的狀態發生變化時,全部訂閱了該變化的對象都會收到通知。
DOM事件是典型的訂閱發佈模式,同時咱們還能夠自定義事件:

var event = {
    clients : {},
    listen : function (signal,fn){
        if(!this.clients[signal]) {
            this.clients[signal] = [];
        }
        this.clients[signal].push(fn);    
    },
    trigger: function (arguments){  //not only trigger the event, but also send some data
        var sig = Array.prototype.shift.call(arguments);
        fns = this.clients[sig];
        
        if(!fns || fns.length === 0)  return false;
        for(var i = 0; i < fns.length; i++) {
            var fn = fns[i];
            fn.apply(this, arguments);
        }
    },
    remove:  function (signal,fn){
        var fns = this.clients[signal];
        if(!fns)  return false;
        if(!fn) {   
            //remove all fns
            delete this.clients[signal];
        } else {
            for(var i = 0; i <fns.length; i++) {
                var _fn = fns[i];
                if(_fn === fn) {
                    fns.splice(i,1);
                }
            } 
        }           
    }
}

event.listen('click',function(data){
    console.log(data);
});

event.trigger('click', "Someone clicked!");

能夠經過離線消息棧來保存沒有被訂閱的可是發生了的事件,等到有人訂閱再依次取出執行。

應用:
網站登陸-當用戶登陸成功而且ajax返回數據後,trigger事件,須要用到用戶數據的渲染模塊訂閱該事件。


模式6-命令模式

能夠解決請求發送者和請求接受者之間的耦合關係。實際上,咱們只須要調用command對象中的execute方法就行,他會自動調用命令接收者對應的命令。

示例:

var tv = {
    open: function() {
        console.log("open tv");
    },
    close: function() {
        console.log("turn off tv");
    },
    nextChannel: function() {
        console.log("next channel");
    }
}

//至關於我把receiver的一些可用操做封裝到command對象裏了,而且提供了統一的接口
var openTVCmd = function(receiver) {
    return {
        execute: function(){
            receiver.open();
        },
        undo : function() {
            //go to previous channel
            
        }
    }
}

var btn1 = document.getElementById("btn1");
var btn2 = document.getElementById("btn2");

var setCmd = function(button, cmd) {
    button.onclick = function(){
        cmd.execute();
    }
}

var opentvcmd = new openTVCmd();
setCmd(btn1, opentvcmd);
btn2.onclick = function(){    //undo command
    opentvcmd.undo();
}

應用:

  1. 可實現命令的撤銷和重作,只需紀錄一個oldState或者使用一個緩存來存放歷史命令;

  2. 可實現命令隊列,將command對象壓入堆棧,只需依次調用他們的execute函數,由此可實現宏命令;

可分爲智能命令和傻瓜命令:
1.智能命令不須要知道receiver,可本身完成請求,代碼上相似策略模式,但目的不一樣;
2.傻瓜命令則只負責將請求傳遞給真正的receiver。


模式7-組合模式

組合模式將對象組合成樹形結構,以表示層級結構。藉助於對象的多態性,它使得用戶能夠統一地對待組合對象(單個對象的組合)和單個對象。

應用:

  1. 可實現宏命令,只須要調用根結點的execute,程序會自動遍歷整棵樹並依次執行各中間節點和葉結點的execute函數。重點是,葉結點與中間結點有統一藉口。

  2. 可用來模擬文件和文件夾層級結構:

示例:

var Folder = function(name) {
    this.name = name;
    this.files = [];
}
Folder.prototype.add = function(file) {
    this.files.push(file);
}
Folder.prototype.scan = function() {
    console.log("begin scanning folder "+ this.name);
    for(var i=0; i<this.files.length; i++) {
        this.files[i].scan();
    }
}

var File = function(name) {
    this.name = name;
}
File.prototype.add = function() {
    throw new Error("cannot add files to a file!");
}

File.prototype.scan = function() {
    console.log("begin scanning "+this.name);
}

var folder1 = new Folder("fo1");
var folder2 = new Folder("fo2");

var file1 = new File("fi1");
var file2 = new File("fi2");

folder1.add(file1);
folder2.add(file2);
folder2.add(folder1);

注意,層級1與層級2結點之間並不是父子關係,只是由於他們有統一的藉口而被聯繫在一塊兒。
能夠對這兩種結點創建雙向映射,即便文件1裏面含有其父結點的引用,這樣子在刪除一個文件時就須要將其在其父結點的files中刪除。

組合模式使得用戶能夠忽略組合對象和單個對象的差別而統一對待,但這也會使得每一個對象看上去都差很少,增長代碼理解的難度。


P.s. 本文總結自《JavaScript設計模式與開發實踐》,曾探著

相關文章
相關標籤/搜索