javascript中經常使用的設計模式,教你寫出更好的前端代碼

經常使用的js設計模式javascript

今天給你們介紹js中經常使用的設計模式,也讓本身對js設計模式有一個更清晰的認識,下面咱們直接進入今日的主題html

經常使用的設計模式

  • 單體模式:
  • 工廠模式:
  • 單例模式
  • 觀察者模式(發佈訂閱模式)
  • 策略模式
  • 模板模式
  • 代理模式
  • 外觀模式

設計模式太多了,貌似有23種,其實咱們在平時的工做中沒有必要特地去用什麼樣的設計模式,或者你在不經意間就已經用了設計模式當中的一種。本文旨在總結平時相對來講用的比較多的設計模式。前端

什麼是設計模式

官方定義

設計模式(Design pattern)是一套被反覆使用、多數人知曉的、通過分類編目的、代碼設計經驗的總結。java

使用設計模式是爲了可重用代碼、讓代碼更容易被他人理解、保證代碼可靠性。 毫無疑問,設計模式於己於他人於系統都是多贏的;設計模式使代碼編制真正工程化;設計模式是軟件工程的基石脈絡,如同大廈的結構同樣。面試

實際狀況

設計模式絕對不是紙上談兵的知識,光看書就覺得本身懂了,那只是井底之蛙之見,設計模式絕對是從實踐中來到實踐中去的!若是編碼經驗不多,也不太可能能理解好設計模式,但凡軟件設計能力強的人編碼功底都是至關紮實的。ajax

  若是沒有能深入理解面向對象,也不太可能理解好設計模式,剛剛畢業或者才工做一兩年就說本身面向對象能力強的人,基本上就是誇誇其談的人。算法

  很明顯,我就是屬於那種誇誇其談的人,哈哈,不過但願對本文的總結,讓本身更加了解這些設計模式,理解的更加透徹。設計模式

單體模式

概念:數組

單體是一個用來劃分命名空間並將一批相關的屬性和方法組織在一塊兒的對象,若是他能夠被實例化,那麼他只能被實例化一次。瀏覽器

特色:

1.能夠來劃分命名空間,從而清除全局變量所帶來的危險。 2.利用分支技術來來封裝瀏覽器之間的差別。 3.能夠把代碼組織的更爲一體,便於閱讀和維護。

代碼實現

/*Basic Singleton*/
var Singleton = {

    attribute:true,

    method1:function(){},

   method2:function(){}
};
複製代碼

應用場景:

  單體模式在咱們平時的應用中用的比較多的,至關於把咱們的代碼封裝在一個起來,只是暴露一個入口,從而避免所有變量的污染。

工廠模式:

概念:

工廠模式的定義:提供建立對象的接口,意思就是根據領導(調用者)的指示(參數),生產相應的產品(對象)。

建立一個對象經常須要複雜的過程,因此不適合在一個複雜的對象中。

建立對象可能會致使大量的重複代碼,也可能提供不了足夠級別的抽象。

工廠就是把成員對象的建立工做轉交給一個外部對象,好處在於消除對象之間的耦合(也就是相互影響)

分類:

簡單工廠模式:使用一個類,一般爲單體,來生成實例。

複雜工廠模式定義是:將其成員對象的實列化推到子類中,子類能夠重寫父類接口方法以便建立的時候指定本身的對象類型。

父類只對建立過程當中的通常性問題進行處理,這些處理會被子類繼承,子類之間是相互獨立的,具體的業務邏輯會放在子類中進行編寫。

代碼實現:

簡單工廠模式: 

var XMLHttpFactory =function(){};      //這是一個簡單工廠模式
  XMLHttpFactory.createXMLHttp =function(){
    var XMLHttp = null;
    if (window.XMLHttpRequest){
      XMLHttp = new XMLHttpRequest()
    }else if (window.ActiveXObject){
      XMLHttp = new ActiveXObject("Microsoft.XMLHTTP")
    }
  return XMLHttp;
  }
  //XMLHttpFactory.createXMLHttp()這個方法根據當前環境的具體狀況返回一個XHR對象。
  var AjaxHander =function(){
    var XMLHttp = XMLHttpFactory.createXMLHttp();
    ...
  }
複製代碼

複雜工廠模式:流程==》 先設計一個抽象類,這個類不能被實例化,只能用來派生子類,最後經過對子類的擴展實現工廠方法

var XMLHttpFactory =function(){};      //這是一個抽象工廠模式

XMLHttpFactory.prototype = {
  //若是真的要調用這個方法會拋出一個錯誤,它不能被實例化,只能用來派生子類
  createFactory:function(){
    throw new Error('This is an abstract class');
  }
}

var XHRHandler =function(){}; //定義一個子類

// 子類繼承父類原型方法
extend( XHRHandler , XMLHttpFactory );

XHRHandler.prototype =new XMLHttpFactory(); //把超類原型引用傳遞給子類,實現繼承

XHRHandler.prototype.constructor = XHRHandler; //重置子類原型的構造器爲子類自身

//從新定義createFactory 方法
XHRHandler.prototype.createFactory =function(){
  var XMLHttp =null;
  if (window.XMLHttpRequest){

    XMLHttp =new XMLHttpRequest();

  }else if (window.ActiveXObject){

    XMLHttp =new ActiveXObject("Microsoft.XMLHTTP")
  }

  return XMLHttp;
}
複製代碼

應用場景:

如下幾種情景下工廠模式特別有用:

(1)對象的構建十分複雜

(2)須要依賴具體環境建立不一樣實例

(3)處理大量具備相同屬性的小對象

優勢:

  能夠實現一些相同的方法,這些相同的方法咱們能夠放在父類中編寫代碼,那麼須要實現具體的業務邏輯,那麼能夠放在子類中重寫該父類的方法,去實現本身的業務邏輯;

  也就是說有兩點:  

  一、弱化對象間的耦合,防止代碼的重複。在一個方法中進行類的實例化,能夠消除重複性的代碼。

  二、重複性的代碼能夠放在父類去編寫,子類繼承於父類的全部成員屬性和方法,子類只專一於實現本身的業務邏輯。

缺點:

當工廠增長到必定程度的時候,提高了代碼的複雜度,可讀性降低。並且沒有解決對象的識別問題,即怎麼知道一個對象的類型。

單例模式

概念:

  單例模式定義了一個對象的建立過程,此對象只有一個單獨的實例,並提供一個訪問它的全局訪問點。也能夠說單例就是保證一個類只有一個實例,實現的方法通常是先判斷實例存在與否,若是存在直接返回,若是不存在就建立了再返回,這就確保了一個類只有一個實例對象。

代碼實現:

  單例的實現有不少種,下面只介紹其中的一種,使用閉包方式來實現單例,代碼以下:

var single = (function(){
    var unique;

    function getInstance(){
    // 若是該實例存在,則直接返回,不然就對其實例化
        if( unique === undefined ){
            unique = new Construct();
        }
        return unique;
    }

    function Construct(){
        // ... 生成單例的構造函數的代碼
    }

    return {
        getInstance : getInstance
    }
})();
複製代碼

使用場景:

單例模式是一種經常使用的模式,有一些對象咱們每每只須要一個,好比全局緩存、瀏覽器的window對象。在js開發中,單例模式的用途一樣很是普遍。試想一下,當咱們
複製代碼

單擊登陸按鈕的時候,頁面中會出現一個登陸框,而這個浮窗是惟一的,不管單擊多少次登陸按鈕,這個浮窗只會被建立一次。所以這個登陸浮窗就適合用單例模式。

總結一下它的使用場景:

  一、能夠用它來劃分命名空間

二、藉助單例模式,能夠把代碼組織的更爲一致,方便閱讀與維護
複製代碼

觀察者模式(發佈訂閱模式)

概念:

  定義對象間的一種一對多的依賴關係,以便當一個對象的狀態發生改變時,全部依賴於它的對象都獲得通知並自動刷新,也被稱爲是發佈訂閱模式。

它須要一種高級的抽象策略,以便訂閱者可以彼此獨立地發生改變,而發行方可以接受任何有消費意向的訂閱者。

應用場景:

  這個模式要先說應用場景,比較好理解。

  打一個離咱們比較近的一個場景,博客園裏面有一個訂閱的按鈕(貌似有bug),好比小A,小B,小C都訂閱了個人博客,當個人博客一有更新時,就會統一發布郵件給他們這三我的,就會通知這些訂閱者

  發佈訂閱模式的流程以下:

  1. 肯定誰是發佈者(好比個人博客)。

  2. 而後給發佈者添加一個緩存列表,用於存放回調函數來通知訂閱者。

  3. 發佈消息,發佈者須要遍歷這個緩存列表,依次觸發裏面存放的訂閱者回調函數。

 四、退訂(好比不想再接收到這些訂閱的信息了,就能夠取消掉)

  

代碼以下:

var pubsub = {};   // 定義發佈者

(function (q) {

    var list = [],  //回調函數存放的數組,也就是記錄有多少人訂閱了咱們東西
        subUid = -1;

    // 發佈消息,遍歷訂閱者
    q.publish = function (type, content) {
        // type 爲文章類型,content爲文章內容

        // 若是沒有人訂閱,直接返回
        if (!list[type]) {

            return false;
        }

        setTimeout(function () {
            var subscribers = list[type],
                len = subscribers ? subscribers.length : 0;

            while (len--) {
                // 將內容注入到訂閱者那裏
                subscribers[len].func(type, content);
            }
        }, 0);

        return true;

    };
    //訂閱方法,由訂閱者來執行
    q.subscribe = function (type, func) {
        // 若是以前沒有訂閱過
        if (!list[type]) {
            list[type] = [];
        }

        // token至關於訂閱者的id,這樣的話若是退訂,咱們就能夠針對它來知道是誰退訂了。
        var token = (++subUid).toString();
        // 每訂閱一個,就把它存入到咱們的數組中去
        list[type].push({
            token: token,
            func: func
        });
        return token;
    };
    //退訂方法
    q.unsubscribe = function (token) {
        for (var m in list) {
            if (list[m]) {
                for (var i = 0, j = list[m].length; i < j; i++) {
                    if (list[m][i].token === token) {
                        list[m].splice(i, 1);
                        return token;
                    }
                }
            }
        }
        return false;
    };

} (pubsub));

//將訂閱賦值給一個變量,以便退訂
var girlA = pubsub.subscribe('js類的文章', function (type, content) {
    console.log('girlA訂閱的'+type + ": 內容內容爲:" + content);
});
var girlB = pubsub.subscribe('js類的文章', function (type, content) {
    console.log('girlB訂閱的'+type + ": 內容內容爲:" + content);
});
var girlC = pubsub.subscribe('js類的文章', function (type, content) {
    console.log('girlC訂閱的'+type + ": 內容內容爲:" + content);
});

//發佈通知
pubsub.publish('js類的文章', '關於js的內容');  
// 輸出:
// girlC訂閱的js類的文章: 內容內容爲:關於js的內容
// test3.html:78 girlB訂閱的js類的文章: 內容內容爲:關於js的內容
// test3.html:75 girlA訂閱的js類的文章: 內容內容爲:關於js的內容


//girlA退訂了關於js類的文章
setTimeout(function () {
    pubsub.unsubscribe(girlA);
}, 0);

//再發布一次,驗證一下是否還可以輸出信息
pubsub.publish('js類的文章', "關於js的第二篇文章");
// 輸出:
// girlB訂閱的js類的文章: 內容內容爲:關於js的第二篇文章
// girlC訂閱的js類的文章: 內容內容爲:關於js的第二篇文章

複製代碼

優缺點:

  優勢:當咱們須要維護相關對象的一致性的時候,使用觀察者模式,,就能夠避免對象之間的緊密耦合。例如,一個對象能夠通知另一個對象,而不須要知道這個對象的信息。

  缺點:在發佈/訂閱模式中,若是咱們須要將發佈者同訂閱者上解耦,將會在一些狀況下,致使很難確保咱們應用中的特定部分按照咱們預期的那樣正常工做。也就是說它的優勢也多是它的缺點

策略模式

概念:

策略模式指的是定義一些列的算法,把他們一個個封裝起來,目的就是將算法的使用與算法的實現分離開來。說白了就是之前要不少判斷的寫法,如今把判斷裏面的內容抽離開來,變成一個個小的個體。 代碼實現:

代碼情景爲超市促銷,vip爲5折,老客戶3折,普通顧客沒折,計算最後須要支付的金額。

沒有使用策略模式的狀況:

function Price(personType, price) {
    //vip 5 折
    if (personType == 'vip') {
        return price * 0.5;
    }
    else if (personType == 'old'){ //老客戶 3 折
        return price * 0.3;
    } else {
        return price; //其餘都全價
    }
}
複製代碼

不足之處:很差的地方,當我有其餘方面的折扣時,又或者我活動的折扣時常常變化的,這樣就要不斷的修改if..else裏面的條件了。並且也違背了設計模式的一個原則:對修改關閉,對擴展開放的原則;

使用策略模式以後:

// 對於vip客戶
function vipPrice() {
    this.discount = 0.5;
}

vipPrice.prototype.getPrice = function(price) {
&emsp;&emsp;return price * this.discount;
}
// 對於老客戶
function oldPrice() {
    this.discount = 0.3;
}

oldPrice.prototype.getPrice = function(price) {
    return price * this.discount;
}
// 對於普通客戶
function Price() {
    this.discount = 1;
}

Price.prototype.getPrice = function(price) {
    return price ;
}

// 上下文,對於客戶端的使用
function Context() {
    this.name = '';
    this.strategy = null;
    this.price = 0;
}

Context.prototype.set = function(name, strategy, price) {
    this.name = name;
    this.strategy = strategy;
    this.price = price;
}
Context.prototype.getResult = function() {
    console.log(this.name + ' 的結帳價爲: ' + this.strategy.getPrice(this.price));
}

var context = new Context();
var vip = new vipPrice();
context.set ('vip客戶', vip, 200);
context.getResult();   // vip客戶 的結帳價爲: 100

var old = new oldPrice();
context.set ('老客戶', old, 200);
context.getResult();  // 老客戶 的結帳價爲: 60

var Price = new Price();
context.set ('普通客戶', Price, 200);
context.getResult();  // 普通客戶 的結帳價爲: 200
複製代碼

經過策略模式,使得客戶的折扣與算法解藕,又使得修改跟擴展能獨立的進行,不影到客戶端或其餘算法的使用;

使用場景:

  策略模式最實用的場合就是某個「類」中包含有大量的條件性語句,好比if...else 或者 switch。每個條件分支都會引發該「類」的特定行爲以不一樣的方式做出改變。以其維

護一段龐大的條件性語句,不如將每個行爲劃分爲多個獨立的對象。每個對象被稱爲一個策略。設置多個這種策略對象,能夠改進咱們的代碼質量,也更好的進行單元測試。

模板模式

概念:

定義了一個操做中的算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類能夠不改變一個算法的結構便可重定義該算法的某些特定步驟。 通俗的講,就是將一些公共方法封裝到父類,子類能夠繼承這個父類,而且能夠在子類中重寫父類的方法,從而實現本身的業務邏輯。 代碼實現:

好比前端面試,基本包括筆試,技術面試,領導面試,HR面試等,可是每一個公司的筆試題,技術面可能不同,也可能同樣,同樣的就繼承父類的方法,不同的就重寫父類的方法

var Interview = function(){};
// 筆試
Interview.prototype.writtenTest = function(){
    console.log("這裏是前端筆試題");
};
// 技術面試
Interview.prototype.technicalInterview = function(){
    console.log("這裏是技術面試");
};
// 領導面試
Interview.prototype.leader = function(){
    console.log("領導面試");
};
// 領導面試
Interview.prototype.HR = function(){
    console.log("HR面試");
};
// 等通知
Interview.prototype.waitNotice = function(){
    console.log("等通知啊,不知道過了沒有哦");
};
// 代碼初始化
Interview.prototype.init = function(){
    this.writtenTest();
    this.technicalInterview();
    this.leader();
    this.HR();
    this.waitNotice();
};

// 阿里巴巴的筆試和技術面不一樣,重寫父類方法,其餘繼承父類方法。
var AliInterview = function(){};
AliInterview.prototype = new Interview();

// 子類重寫方法 實現本身的業務邏輯
AliInterview.prototype.writtenTest = function(){
    console.log("阿里的技術題就是難啊");
}
AliInterview.prototype.technicalInterview = function(){
    console.log("阿里的技術面就是叼啊");
}
var AliInterview = new AliInterview();
AliInterview.init();

// 阿里的技術題就是難啊
// 阿里的技術面就是叼啊
// 領導面試
// HR面試
// 等通知啊,不知道過了沒有哦
複製代碼

應用場景:

  模板模式主要應用在一些代碼剛開要一次性實現不變的部分。可是未來頁面有修改,須要更改業務邏輯的部分或者從新添加新業務的狀況。主要是經過子類來改寫父類的情

況,其餘不須要改變的部分繼承父類。

代理模式

概念:

  代理模式的中文含義就是幫別人作事,javascript的解釋爲:把對一個對象的訪問, 交給另外一個代理對象來操做.

代碼實現:

 好比咱們公司的補打卡是最後是要交給大boss來審批的,可是公司那麼多人,天天都那麼多補打卡,那大boss豈不是被這些雜事累死。因此大boss下會有一個助理,來幫

忙作這個審批,最後再將每月的補打卡統一交給大boss看看就行。

// 補打卡事件
var fillOut = function (lateDate) {

    this.lateDate = lateDate;
};

// 這是bigBoss
var bigBoss = function (fillOut) {

    this.state = function (isSuccess) {
        console.log("忘記打卡的日期爲:" + fillOut.lateDate + ", 補打卡狀態:" + isSuccess);
    }
};
// 助理代理大boss 完成補打卡審批
var proxyAssis = function (fillOut) {

    this.state = function (isSuccess) {
        (new bigBoss(fillOut)).state(isSuccess); // 替bigBoss審批
    }
};

// 調用方法:
var proxyAssis = new proxyAssis(new fillOut("2016-9-11"));
proxyAssis.state("補打卡成功");

// 忘記打卡的日期爲:2016-9-11, 補打卡狀態:補打卡成功
複製代碼

應用場景:

  好比圖片的懶加載,咱們就能夠運用這種技術。在圖片未加載完成以前,給個loading圖片,加載完成後再替換成實體路徑。

var myImage = (function(){
    var imgNode = document.createElement("img");
    document.body.appendChild(imgNode);
    return function(src){
        imgNode.src = src;
    }
})();
// 代理模式
var ProxyImage = (function(){
    var img = new Image();
    img.onload = function(){
        myImage(this.src);
    };
    return function(src) {
                // 佔位圖片loading
                myImage("http://img.lanrentuku.com/img/allimg/1212/5-121204193Q9-50.gif");
        img.src = src;
    }
})();
// 調用方式

ProxyImage("https://img.alicdn.com/tps/i4/TB1b_neLXXXXXcoXFXXc8PZ9XXX-130-200.png"); // 真實要展現的圖片
複製代碼

固然,這種懶加載方法不用代理模式也是能夠實現的,只是用代理模式。咱們可讓 myImage 只作一件事,只負責將實際圖片加入到頁面中,而loading圖片交給ProxyImage去作。從而下降代碼的耦合度。由於當我不想用loading的時候,能夠直接調用myImage 方法。也便是說假如我門不須要代理對象的話,直接能夠換成本體對象調用該方法便可。

外觀模式

概念:

  外觀模式是很常見。其實它就是經過編寫一個單獨的函數,來簡化對一個或多個更大型的,可能更爲複雜的函數的訪問。也就是說能夠視外觀模式爲一種簡化某些內容的手段。

  說白了,外觀模式就是一個函數,封裝了複雜的操做。

代碼實現:

  好比一個跨瀏覽器的ajax調用

function ajaxCall(type,url,callback,data){
    // 根據當前瀏覽器獲取對ajax鏈接對象的引用
    var xhr=(function(){
        try {
            // 全部現代瀏覽器所使用的標準方法
            return new XMLHttpRequest();

        }catch(e){}

        // 較老版本的internet Explorer兼容
        try{

            return new ActiveXObject("Msxml2.XMLHTTP.6.0");

        }catch(e){}

        try{

            return new ActiveXObject("Msxml2.XMLHTTP.3.0");

        }catch(e){}

        try{

            return new ActiveXObject("Microsoft.XMLHTTP");

        }catch(e){}

        // 若是沒能找到相關的ajax鏈接對象,則跑出一個錯誤。
        throw new Error("Ajax not support in this browser.")

    }()),
    STATE_LOADED=4,
    STATUS_OK=200;

    // 一但從服務器收到表示成功的相應消息,則執行所給定的回調方法
    xhr.onreadystatechange=function{
        if(xhr.readyState !==STATE_LOADED){
            return;
        }
        if(xhr.state==STATUS_OK){
            callback(xhr.responseText);
        }
    }

    // 使用瀏覽器的ajax鏈接對象來向所給定的URL發出相關的調用
    xhr.open(type.toUpperCase(),url);
    xhr.send(data);
}

// 使用方法
ajaxCall("get","/user/12345",function(rs){
    alert('收到的數據爲:'+rs);
})
複製代碼

應用場景:

  當須要經過一個單獨的函數或方法來訪問一系列的函數或方法調用,以簡化代碼庫的其他內容,使得代碼更容易跟蹤管理或者更好的維護時,可使用外觀模式。其實咱們平時代碼中這種模式應該是用的比較多的。

javascript的設計模式有不少種,本文只是總結了其中的幾種,之後可能會補充。這篇文章下來查閱了挺多資料,也學到挺多東西的。

相關文章
相關標籤/搜索