Javascript經常使用的設計模式詳解

Javascript經常使用的設計模式詳解javascript

閱讀目錄php

一:理解工廠模式css

   工廠模式相似於現實生活中的工廠能夠產生大量類似的商品,去作一樣的事情,實現一樣的效果;這時候須要使用工廠模式。html

   簡單的工廠模式能夠理解爲解決多個類似的問題;這也是她的優勢;好比以下代碼: 前端

複製代碼
function CreatePerson(name,age,sex) {
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.sex = sex;
    obj.sayName = function(){
        return this.name;
    }
    return obj;
}
var p1 = new CreatePerson("longen",'28','男');
var p2 = new CreatePerson("tugenhua",'27','女');
console.log(p1.name); // longen
console.log(p1.age);  // 28
console.log(p1.sex);  //
console.log(p1.sayName()); // longen

console.log(p2.name);  // tugenhua
console.log(p2.age);   // 27
console.log(p2.sex);   //
console.log(p2.sayName()); // tugenhua

// 返回都是object 沒法識別對象的類型 不知道他們是哪一個對象的實列
console.log(typeof p1);  // object
console.log(typeof p2);  // object
console.log(p1 instanceof Object); // true
複製代碼

如上代碼:函數CreatePerson能接受三個參數name,age,sex等參數,能夠無數次調用這個函數,每次返回都會包含三個屬性和一個方法的對象。java

工廠模式是爲了解決多個相似對象聲明的問題;也就是爲了解決實列化對象產生重複的問題。jquery

優勢:能解決多個類似的問題。面試

缺點:不能知道對象識別的問題(對象的類型不知道)ajax

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

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

 父類就變成了一個抽象類,可是父類能夠執行子類中相同相似的方法,具體的業務邏輯須要放在子類中去實現;好比我如今開幾個自行車店,那麼每一個店都有幾種型號的自行車出售。咱們如今來使用工廠模式來編寫這些代碼;

父類的構造函數以下

複製代碼
// 定義自行車的構造函數
var BicycleShop = function(){};
BicycleShop.prototype = {
    constructor: BicycleShop,
    /*
    * 買自行車這個方法
    * @param {model} 自行車型號
    */
    sellBicycle: function(model){
        var bicycle = this.createBicycle(mode);
        // 執行A業務邏輯
        bicycle.A();

        // 執行B業務邏輯
        bicycle.B();

        return bicycle;
    },
    createBicycle: function(model){
        throw new Error("父類是抽象類不能直接調用,須要子類重寫該方法");
    }
};
複製代碼

上面是定義一個自行車抽象類來編寫工廠模式的實列,定義了createBicycle這個方法,可是若是直接實例化父類,調用父類中的這個createBicycle方法,會拋出一個error,由於父類是一個抽象類,他不能被實列化,只能經過子類來實現這個方法,實現本身的業務邏輯,下面咱們來定義子類,咱們學會如何使用工廠模式從新編寫這個方法,首先咱們須要繼承父類中的成員,而後編寫子類;以下代碼:

複製代碼
// 定義自行車的構造函數
var BicycleShop = function(name){
    this.name = name;
    this.method = function(){
        return this.name;
    }
};
BicycleShop.prototype = {
    constructor: BicycleShop,
    /*
     * 買自行車這個方法
     * @param {model} 自行車型號
    */
    sellBicycle: function(model){
            var bicycle = this.createBicycle(model);
            // 執行A業務邏輯
            bicycle.A();

            // 執行B業務邏輯
            bicycle.B();

            return bicycle;
        },
        createBicycle: function(model){
            throw new Error("父類是抽象類不能直接調用,須要子類重寫該方法");
        }
    };
    // 實現原型繼承
    function extend(Sub,Sup) {
        //Sub表示子類,Sup表示超類
        // 首先定義一個空函數
        var F = function(){};

        // 設置空函數的原型爲超類的原型
        F.prototype = Sup.prototype; 

        // 實例化空函數,並把超類原型引用傳遞給子類
        Sub.prototype = new F();
                    
        // 重置子類原型的構造器爲子類自身
        Sub.prototype.constructor = Sub;
                    
        // 在子類中保存超類的原型,避免子類與超類耦合
        Sub.sup = Sup.prototype;

        if(Sup.prototype.constructor === Object.prototype.constructor) {
            // 檢測超類原型的構造器是否爲原型自身
            Sup.prototype.constructor = Sup;
        }
    }
    var BicycleChild = function(name){
        this.name = name;
// 繼承構造函數父類中的屬性和方法
        BicycleShop.call(this,name);
    };
    // 子類繼承父類原型方法
    extend(BicycleChild,BicycleShop);
// BicycleChild 子類重寫父類的方法
BicycleChild.prototype.createBicycle = function(){
    var A = function(){
        console.log("執行A業務操做");    
    };
    var B = function(){
        console.log("執行B業務操做");
    };
    return {
        A: A,
        B: B
    }
}
var childClass = new BicycleChild("龍恩");
console.log(childClass);
複製代碼

實例化子類,而後打印出該實例以下截圖所示:

console.log(childClass.name);  // 龍恩

// 下面是實例化後 執行父類中的sellBicycle這個方法後會依次調用父類中的A

// B方法;A方法和B方法依次在子類中去編寫具體的業務邏輯。

childClass.sellBicycle("mode"); // 打印出  執行A業務操做和執行B業務操做

上面只是"龍恩"自行車這麼一個型號的,若是須要生成其餘型號的自行車的話,能夠編寫其餘子類,工廠模式最重要的優勢是:能夠實現一些相同的方法,這些相同的方法咱們能夠放在父類中編寫代碼,那麼須要實現具體的業務邏輯,那麼能夠放在子類中重寫該父類的方法,去實現本身的業務邏輯;使用專業術語來說的話有2點:第一:弱化對象間的耦合,防止代碼的重複。在一個方法中進行類的實例化,能夠消除重複性的代碼。第二:重複性的代碼能夠放在父類去編寫,子類繼承於父類的全部成員屬性和方法,子類只專一於實現本身的業務邏輯。

二:理解單體模式

單體模式提供了一種將代碼組織爲一個邏輯單元的手段,這個邏輯單元中的代碼能夠經過單一變量進行訪問。

單體模式的優勢是:

  1. 能夠用來劃分命名空間,減小全局變量的數量。
  2. 使用單體模式可使代碼組織的更爲一致,使代碼容易閱讀和維護。
  3. 能夠被實例化,且實例化一次。

什麼是單體模式?單體模式是一個用來劃分命名空間並將一批屬性和方法組織在一塊兒的對象,若是它能夠被實例化,那麼它只能被實例化一次。

可是並不是全部的對象字面量都是單體,好比說模擬數組或容納數據的話,那麼它就不是單體,可是若是是組織一批相關的屬性和方法在一塊兒的話,那麼它有多是單體模式,因此這須要看開發者編寫代碼的意圖;

下面咱們來看看定義一個對象字面量(結構相似於單體模式)的基本結構以下:

複製代碼
// 對象字面量
var Singleton = {
    attr1: 1,
    attr2: 2,
    method1: function(){
        return this.attr1;
    },
    method2: function(){
        return this.attr2;
    }
};
複製代碼

如上面只是簡單的字面量結構,上面的全部成員變量都是經過Singleton來訪問的,可是它並非單體模式;由於單體模式還有一個更重要的特色,就是能夠僅被實例化一次,上面的只是不能被實例化的一個類,所以不是單體模式;對象字面量是用來建立單體模式的方法之一;

使用單體模式的結構以下demo

咱們明白的是單體模式若是有實例化的話,那麼只實例化一次,要實現一個單體模式的話,咱們無非就是使用一個變量來標識該類是否被實例化,若是未被實例化的話,那麼咱們能夠實例化一次,不然的話,直接返回已經被實例化的對象。

以下代碼是單體模式的基本結構:

複製代碼
// 單體模式
var Singleton = function(name){
    this.name = name;
    this.instance = null;
};
Singleton.prototype.getName = function(){
    return this.name;
}
// 獲取實例對象
function getInstance(name) {
    if(!this.instance) {
        this.instance = new Singleton(name);
    }
    return this.instance;
}
// 測試單體模式的實例
var a = getInstance("aa");
var b = getInstance("bb");
複製代碼

// 由於單體模式是隻實例化一次,因此下面的實例是相等的

console.log(a === b); // true

因爲單體模式只實例化一次,所以第一次調用,返回的是a實例對象,當咱們繼續調用的時候,b的實例就是a的實例,所以下面都是打印的是aa

console.log(a.getName());// aa

console.log(b.getName());// aa

上面的封裝單體模式也能夠改爲以下結構寫法:

複製代碼
// 單體模式
var Singleton = function(name){
    this.name = name;
};
Singleton.prototype.getName = function(){
    return this.name;
}
// 獲取實例對象
var getInstance = (function() {
    var instance = null;
    return function(name) {
        if(!instance) {
            instance = new Singleton(name);
        }
        return instance;
    }
})();
// 測試單體模式的實例
var a = getInstance("aa");
var b = getInstance("bb");
複製代碼

// 由於單體模式是隻實例化一次,因此下面的實例是相等的

console.log(a === b); // true

console.log(a.getName());// aa

console.log(b.getName());// aa

理解使用代理實現單列模式的好處
    好比我如今頁面上須要建立一個div的元素,那麼咱們確定須要有一個建立div的函數,而如今我只須要這個函數只負責建立div元素,其餘的它不想管,也就是想實現單一職責原則,就比如淘寶的kissy同樣,一開始的時候他們定義kissy只作一件事,而且把這件事作好,具體的單體模式中的實例化類的事情交給代理函數去處理,這樣作的好處是具體的業務邏輯分開了,代理只管代理的業務邏輯,在這裏代理的做用是實例化對象,而且只實例化一次建立div代碼只管建立div,其餘的無論;以下代碼:

複製代碼
// 單體模式
var CreateDiv = function(html) {
    this.html = html;
    this.init();
}
CreateDiv.prototype.init = function(){
    var div = document.createElement("div");
    div.innerHTML = this.html;
    document.body.appendChild(div);
};
// 代理實現單體模式
var ProxyMode = (function(){
    var instance;
    return function(html) {
        if(!instance) {
            instance = new CreateDiv("我來測試下");
        }
        return instance;
    } 
})();
var a = new ProxyMode("aaa");
var b = new ProxyMode("bbb");
console.log(a===b);// true
複製代碼

理解使用單體模式來實現彈窗的基本原理

下面咱們繼續來使用單體模式來實現一個彈窗的demo;咱們先不討論使用單體模式來實現,咱們想下咱們平時是怎麼編寫代碼來實現彈窗效果的; 好比咱們有一個彈窗,默認的狀況下確定是隱藏的,當我點擊的時候,它須要顯示出來;以下編寫代碼:

複製代碼
// 實現彈窗
var createWindow = function(){
    var div = document.createElement("div");
    div.innerHTML = "我是彈窗內容";
    div.style.display = 'none';
    document.body.appendChild('div');
    return div;
};
document.getElementById("Id").onclick = function(){
    // 點擊後先建立一個div元素
    var win = createWindow();
    win.style.display = "block";
}
複製代碼

如上的代碼;你們能夠看看,有明顯的缺點,好比我點擊一個元素須要建立一個div,我點擊第二個元素又會建立一次div,咱們頻繁的點擊某某元素,他們會頻繁的建立div的元素,雖然當咱們點擊關閉的時候能夠移除彈出代碼,可是呢咱們頻繁的建立和刪除並很差,特別對於性能會有很大的影響,對DOM頻繁的操做會引發重繪等,從而影響性能;所以這是很是很差的習慣;咱們如今可使用單體模式來實現彈窗效果,咱們只實例化一次就能夠了;以下代碼:

複製代碼
// 實現單體模式彈窗
var createWindow = (function(){
    var div;
    return function(){
        if(!div) {
            div = document.createElement("div");
            div.innerHTML = "我是彈窗內容";
            div.style.display = 'none';
            document.body.appendChild(div);
        }
        return div;
    }
})();
document.getElementById("Id").onclick = function(){
    // 點擊後先建立一個div元素
    var win = createWindow();
    win.style.display = "block";
}
複製代碼

理解編寫通用的單體模式

上面的彈窗的代碼雖然完成了使用單體模式建立彈窗效果,可是代碼並不通用,好比上面是完成彈窗的代碼,假如咱們之後須要在頁面中一個iframe呢?咱們是否是須要從新寫一套建立iframe的代碼呢?好比以下建立iframe:

複製代碼
var createIframe = (function(){
    var iframe;
    return function(){
        if(!iframe) {
            iframe = document.createElement("iframe");
            iframe.style.display = 'none';
            document.body.appendChild(iframe);
        }
        return iframe;
    };
})();
複製代碼

咱們看到如上代碼,建立div的代碼和建立iframe代碼很相似,咱們如今能夠考慮把通用的代碼分離出來,使代碼變成徹底抽象,咱們如今能夠編寫一套代碼封裝在getInstance函數內,以下代碼:

複製代碼
var getInstance = function(fn) {
    var result;
    return function(){
        return result || (result = fn.call(this,arguments));
    }
};
複製代碼

如上代碼:咱們使用一個參數fn傳遞進去,若是有result這個實例的話,直接返回,不然的話,當前的getInstance函數調用fn這個函數,是this指針指向與這個fn這個函數;以後返回被保存在result裏面;如今咱們能夠傳遞一個函數進去,無論他是建立div也好,仍是建立iframe也好,總之若是是這種的話,均可以使用getInstance來獲取他們的實例對象;

以下測試建立iframe和建立div的代碼以下:

複製代碼
// 建立div
var createWindow = function(){
    var div = document.createElement("div");
    div.innerHTML = "我是彈窗內容";
    div.style.display = 'none';
    document.body.appendChild(div);
    return div;
};
// 建立iframe
var createIframe = function(){
    var iframe = document.createElement("iframe");
    document.body.appendChild(iframe);
    return iframe;
};
// 獲取實例的封裝代碼
var getInstance = function(fn) {
    var result;
    return function(){
        return result || (result = fn.call(this,arguments));
    }
};
// 測試建立div
var createSingleDiv = getInstance(createWindow);
document.getElementById("Id").onclick = function(){
    var win = createSingleDiv();
    win.style.display = "block";
};
// 測試建立iframe
var createSingleIframe = getInstance(createIframe);
document.getElementById("Id").onclick = function(){
    var win = createSingleIframe();
    win.src = "http://cnblogs.com";
};
複製代碼

三:理解模塊模式

咱們經過單體模式理解了是以對象字面量的方式來建立單體模式的;好比以下的對象字面量的方式代碼以下:

複製代碼
var singleMode = {
    name: value,
    method: function(){
                
    }
};
複製代碼

模塊模式的思路是爲單體模式添加私有變量和私有方法可以減小全局變量的使用;以下就是一個模塊模式的代碼結構:

複製代碼
var singleMode = (function(){
    // 建立私有變量
    var privateNum = 112;
    // 建立私有函數
    function privateFunc(){
        // 實現本身的業務邏輯代碼
    }
    // 返回一個對象包含公有方法和屬性
    return {
        publicMethod1: publicMethod1,
        publicMethod2: publicMethod1
    };
})();
複製代碼

   模塊模式使用了一個返回對象的匿名函數。在這個匿名函數內部,先定義了私有變量和函數,供內部函數使用,而後將一個對象字面量做爲函數的值返回,返回的對象字面量中只包含能夠公開的屬性和方法。這樣的話,能夠提供外部使用該方法;因爲該返回對象中的公有方法是在匿名函數內部定義的,所以它能夠訪問內部的私有變量和函數。

咱們何時使用模塊模式?

若是咱們必須建立一個對象並以某些數據進行初始化,同時還要公開一些可以訪問這些私有數據的方法,那麼咱們這個時候就可使用模塊模式了。

理解加強的模塊模式

加強的模塊模式的使用場合是:適合那些單列必須是某種類型的實例,同時還必須添加某些屬性或方法對其加以加強的狀況。好比以下代碼:

複製代碼
function CustomType() {
    this.name = "tugenhua";
};
CustomType.prototype.getName = function(){
    return this.name;
}
var application = (function(){
    // 定義私有
    var privateA = "aa";
    // 定義私有函數
    function A(){};

    // 實例化一個對象後,返回該實例,而後爲該實例增長一些公有屬性和方法
    var object = new CustomType();

    // 添加公有屬性
    object.A = "aa";
    // 添加公有方法
    object.B = function(){
        return privateA;
    }
    // 返回該對象
    return object;
})();
複製代碼

下面咱們來打印下application該對象;以下:

console.log(application);

繼續打印該公有屬性和方法以下:

console.log(application.A);// aa

console.log(application.B()); // aa

console.log(application.name); // tugenhua

console.log(application.getName());// tugenhua

四:理解代理模式

     代理是一個對象,它能夠用來控制對本體對象的訪問,它與本體對象實現了一樣的接口,代理對象會把全部的調用方法傳遞給本體對象的;代理模式最基本的形式是對訪問進行控制,而本體對象則負責執行所分派的那個對象的函數或者類,簡單的來說本地對象注重的去執行頁面上的代碼,代理則控制本地對象什麼時候被實例化,什麼時候被使用;咱們在上面的單體模式中使用過一些代理模式,就是使用代理模式實現單體模式的實例化,其餘的事情就交給本體對象去處理;

代理的優勢:

  1. 代理對象能夠代替本體被實例化,並使其能夠被遠程訪問;
  2. 它還能夠把本體實例化推遲到真正須要的時候;對於實例化比較費時的本體對象,或者由於尺寸比較大以致於不用時不適於保存在內存中的本體,咱們能夠推遲實例化該對象;

咱們先來理解代理對象代替本體對象被實例化的列子;好比如今京東ceo想送給奶茶妹一個禮物,可是呢假如該ceo很差意思送,或者因爲工做忙沒有時間送,那麼這個時候他就想委託他的經紀人去作這件事,因而咱們可使用代理模式來編寫以下代碼:

複製代碼
// 先申明一個奶茶妹對象
var TeaAndMilkGirl = function(name) {
    this.name = name;
};
// 這是京東ceo先生
var Ceo = function(girl) {
    this.girl = girl;
    // 送結婚禮物 給奶茶妹
    this.sendMarriageRing = function(ring) {
        console.log("Hi " + this.girl.name + ", ceo送你一個禮物:" + ring);
    }
};
// 京東ceo的經紀人是代理,來代替送
var ProxyObj = function(girl){
    this.girl = girl;
    // 經紀人代理送禮物給奶茶妹
    this.sendGift = function(gift) {
        // 代理模式負責本體對象實例化
        (new Ceo(this.girl)).sendMarriageRing(gift);
    }
};
// 初始化
var proxy = new ProxyObj(new TeaAndMilkGirl("奶茶妹"));
proxy.sendGift("結婚戒"); // Hi 奶茶妹, ceo送你一個禮物:結婚戒
複製代碼

代碼如上的基本結構,TeaAndMilkGirl 是一個被送的對象(這裏是奶茶妹);Ceo 是送禮物的對象,他保存了奶茶妹這個屬性,及有一個本身的特權方法sendMarriageRing 就是送禮物給奶茶妹這麼一個方法;而後呢他是想經過他的經紀人去把這件事完成,因而須要建立一個經濟人的代理模式,名字叫ProxyObj ;他的主要作的事情是,把ceo交給他的禮物送給ceo的情人,所以該對象一樣須要保存ceo情人的對象做爲本身的屬性,同時也須要一個特權方法sendGift ,該方法是送禮物,所以在該方法內能夠實例化本體對象,這裏的本體對象是ceo送花這件事情,所以須要實例化該本體對象後及調用本體對象的方法(sendMarriageRing).

最後咱們初始化是須要代理對象ProxyObj;調用ProxyObj 對象的送花這個方法(sendGift)便可;

對於咱們提到的優勢,第二點的話,咱們下面能夠來理解下虛擬代理,虛擬代理用於控制對那種建立開銷很大的本體訪問,它會把本體的實例化推遲到有方法被調用的時候;好比說如今有一個對象的實例化很慢的話,不能在網頁加載的時候當即完成,咱們能夠爲其建立一個虛擬代理,讓他把該對象的實例推遲到須要的時候。

理解使用虛擬代理實現圖片的預加載

在網頁開發中,圖片的預加載是一種比較經常使用的技術,若是直接給img標籤節點設置src屬性的話,若是圖片比較大的話,或者網速相對比較慢的話,那麼在圖片未加載完以前,圖片會有一段時間是空白的場景,這樣對於用戶體驗來說並很差,那麼這個時候咱們能夠在圖片未加載完以前咱們可使用一個loading加載圖片來做爲一個佔位符,來提示用戶該圖片正在加載,等圖片加載完後咱們能夠對該圖片直接進行賦值便可;下面咱們先不用代理模式來實現圖片的預加載的狀況下代碼以下:

第一種方案:不使用代理的預加載圖片函數以下

複製代碼
// 不使用代理的預加載圖片函數以下
var myImage = (function(){
    var imgNode = document.createElement("img");
    document.body.appendChild(imgNode);
    var img = new Image();
    img.onload = function(){
        imgNode.src = this.src;
    };
    return {
        setSrc: function(src) {
            imgNode.src = "http://img.lanrentuku.com/img/allimg/1212/5-121204193Q9-50.gif";
            img.src = src;
        }
    }
})();
// 調用方式
myImage.setSrc("https://img.alicdn.com/tps/i4/TB1b_neLXXXXXcoXFXXc8PZ9XXX-130-200.png");
複製代碼

如上代碼是不使用代理模式來實現的代碼;

第二種方案:使用代理模式來編寫預加載圖片的代碼以下:

複製代碼
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("http://img.lanrentuku.com/img/allimg/1212/5-121204193Q9-50.gif");
        img.src = src;
        }
    }
})();
// 調用方式
ProxyImage.setSrc("https://img.alicdn.com/tps/i4/TB1b_neLXXXXXcoXFXXc8PZ9XXX-130-200.png");
複製代碼

第一種方案是使用通常的編碼方式實現圖片的預加載技術,首先建立imgNode元素,而後調用myImage.setSrc該方法的時候,先給圖片一個預加載圖片,當圖片加載完的時候,再給img元素賦值,第二種方案是使用代理模式來實現的,myImage 函數只負責建立img元素,代理函數ProxyImage 負責給圖片設置loading圖片,當圖片真正加載完後的話,調用myImage中的myImage.setSrc方法設置圖片的路徑;他們之間的優缺點以下:

  1. 第一種方案通常的方法代碼的耦合性過高,一個函數內負責作了幾件事情,好比建立img元素,和實現給未加載圖片完成以前設置loading加載狀態等多項事情,未知足面向對象設計原則中單一職責原則;而且當某個時候不須要代理的時候,須要從myImage 函數內把代碼刪掉,這樣代碼耦合性過高。
  2. 第二種方案使用代理模式,其中myImage 函數只負責作一件事,建立img元素加入到頁面中,其中的加載loading圖片交給代理函數ProxyImage 去作,當圖片加載成功後,代理函數ProxyImage 會通知及執行myImage 函數的方法,同時當之後不須要代理對象的話,咱們直接能夠調用本體對象的方法便可;

從上面代理模式咱們能夠看到,代理模式和本體對象中有相同的方法setSrc,這樣設置的話有以下2個優勢:

  1. 用戶能夠放心地請求代理,他們只關心是否能獲得想要的結果。假如我門不須要代理對象的話,直接能夠換成本體對象調用該方法便可。
  2. 在任何使用本體對象的地方均可以替換成使用代理。

固然若是代理對象和本體對象都返回一個匿名函數的話,那麼也能夠認爲他們也具備一直的接口;好比以下代碼:

複製代碼
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) {
                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");
複製代碼

虛擬代理合並http請求的理解:

   好比在作後端系統中,有表格數據,每一條數據前面有複選框按鈕,當點擊複選框按鈕時候,須要獲取該id後須要傳遞給給服務器發送ajax請求,服務器端須要記錄這條數據,去請求,若是咱們每當點擊一下向服務器發送一個http請求的話,對於服務器來講壓力比較大,網絡請求比較頻繁,可是若是如今該系統的實時數據不是很高的話,咱們能夠經過一個代理函數收集一段時間內(好比說2-3秒)的全部id,一次性發ajax請求給服務器,相對來講網絡請求下降了, 服務器壓力減小了;

複製代碼
// 首先html結構以下:
<p>
    <label>選擇框</label>
    <input type="checkbox" class="j-input" data-id="1"/>
</p>
<p>
    <label>選擇框</label>
    <input type="checkbox" class="j-input" data-id = "2"/>
</p>
<p>
    <label>選擇框</label>
    <input type="checkbox" class="j-input" data-id="3"/>
</p>
<p>
    <label>選擇框</label>
    <input type="checkbox" class="j-input" data-id = "4"/>
</p>
複製代碼

通常的狀況下 JS以下編寫

複製代碼
<script>
    var checkboxs = document.getElementsByClassName("j-input");
    for(var i = 0,ilen = checkboxs.length; i < ilen; i+=1) {
        (function(i){
            checkboxs[i].onclick = function(){
                if(this.checked) {
                    var id = this.getAttribute("data-id");
                    // 以下是ajax請求
                }
            }
        })(i);
    }
</script>
複製代碼

下面咱們經過虛擬代理的方式,延遲2秒,在2秒後獲取全部被選中的複選框的按鈕id,一次性給服務器發請求。

  經過點擊頁面的複選框,選中的時候增長一個屬性isflag,沒有選中的時候刪除該屬性isflag,而後延遲個2秒,在2秒後從新判斷頁面上全部複選框中有isflag的屬性上的id,存入數組,而後代理函數調用本體函數的方法,把延遲2秒後的全部id一次性發給本體方法,本體方法能夠獲取全部的id,能夠向服務器端發送ajax請求,這樣的話,服務器的請求壓力相對來講減小了。

代碼以下:

複製代碼
// 本體函數
var mainFunc = function(ids) {
    console.log(ids); // 便可打印被選中的全部的id
    // 再把全部的id一次性發ajax請求給服務器端
};
// 代理函數 經過代理函數獲取全部的id 傳給本體函數去執行
var proxyFunc = (function(){
    var cache = [],  // 保存一段時間內的id
        timer = null; // 定時器
    return function(checkboxs) {
        // 判斷若是定時器有的話,不進行覆蓋操做
        if(timer) {
            return;
        }
        timer = setTimeout(function(){
            // 在2秒內獲取全部被選中的id,經過屬性isflag判斷是否被選中
            for(var i = 0,ilen = checkboxs.length; i < ilen; i++) {
                if(checkboxs[i].hasAttribute("isflag")) {
                    var id = checkboxs[i].getAttribute("data-id");
                    cache[cache.length] = id;
                }
            }
            mainFunc(cache.join(',')); // 2秒後須要給本體函數傳遞全部的id
            // 清空定時器
            clearTimeout(timer);
            timer = null;
            cache = [];
        },2000);
    }
})();
var checkboxs = document.getElementsByClassName("j-input");
for(var i = 0,ilen = checkboxs.length; i < ilen; i+=1) {
    (function(i){
        checkboxs[i].onclick = function(){
            if(this.checked) {
                // 給當前增長一個屬性
                this.setAttribute("isflag",1);
            }else {
                this.removeAttribute('isflag');
            }
            // 調用代理函數
            proxyFunc(checkboxs);
        }
    })(i);
}
複製代碼

理解緩存代理:

   緩存代理的含義就是對第一次運行時候進行緩存,當再一次運行相同的時候,直接從緩存裏面取,這樣作的好處是避免重複一次運算功能,若是運算很是複雜的話,對性能很耗費,那麼使用緩存對象能夠提升性能;咱們能夠先來理解一個簡單的緩存列子,就是網上常見的加法和乘法的運算。代碼以下:

複製代碼
// 計算乘法
var mult = function(){
    var a = 1;
    for(var i = 0,ilen = arguments.length; i < ilen; i+=1) {
        a = a*arguments[i];
    }
    return a;
};
// 計算加法
var plus = function(){
    var a = 0;
    for(var i = 0,ilen = arguments.length; i < ilen; i+=1) {
        a += arguments[i];
    }
    return a;
}
// 代理函數
var proxyFunc = function(fn) {
    var cache = {};  // 緩存對象
    return function(){
        var args = Array.prototype.join.call(arguments,',');
        if(args in cache) {
            return cache[args];   // 使用緩存代理
        }
        return cache[args] = fn.apply(this,arguments);
    }
};
var proxyMult = proxyFunc(mult);
console.log(proxyMult(1,2,3,4)); // 24
console.log(proxyMult(1,2,3,4)); // 緩存取 24

var proxyPlus = proxyFunc(plus);
console.log(proxyPlus(1,2,3,4));  // 10
console.log(proxyPlus(1,2,3,4));  // 緩存取 10
複製代碼

五:理解職責鏈模式

優勢是:消除請求的發送者與接收者之間的耦合。

    職責連是由多個不一樣的對象組成的,發送者是發送請求的對象,而接收者則是鏈中那些接收這種請求而且對其進行處理或傳遞的對象。請求自己有時候也能夠是一個對象,它封裝了和操做有關的全部數據,基本實現流程以下:

1. 發送者知道鏈中的第一個接收者,它向這個接收者發送該請求。

2. 每個接收者都對請求進行分析,而後要麼處理它,要麼它往下傳遞。

3. 每個接收者知道其餘的對象只有一個,即它在鏈中的下家(successor)。

4. 若是沒有任何接收者處理請求,那麼請求會從鏈中離開。

   咱們能夠理解職責鏈模式是處理請求組成的一條鏈,請求在這些對象之間依次傳遞,直到遇到一個能夠處理它的對象,咱們把這些對象稱爲鏈中的節點。好比對象A給對象B發請求,若是B對象不處理,它就會把請求交給C,若是C對象不處理的話,它就會把請求交給D,依次類推,直到有一個對象能處理該請求爲止,固然沒有任何對象處理該請求的話,那麼請求就會從鏈中離開。

   好比常見的一些外包公司接到一個項目,那麼接到項目有多是公司的負責項目的人或者經理級別的人,經理接到項目後本身不開發,直接把它交到項目經理來開發,項目經理本身確定不樂意本身動手開發哦,它就把項目交給下面的碼農來作,因此碼農來處理它,若是碼農也不處理的話,那麼這個項目可能會直接掛掉了,可是最後完成後,外包公司它並不知道這些項目中的那一部分具體有哪些人開發的,它並不知道,也並不關心的,它關心的是這個項目已交給外包公司已經開發完成了且沒有任何bug就能夠了;因此職責鏈模式的優勢就在這裏:

消除請求的發送者(須要外包項目的公司)與接收者(外包公司)之間的耦合。

下面列舉個列子來講明職責鏈的好處:

天貓每一年雙11都會作抽獎活動的,好比阿里巴巴想提升你們使用支付寶來支付的話,每一位用戶充值500元到支付寶的話,那麼能夠100%中獎100元紅包,

充值200元到支付寶的話,那麼能夠100%中獎20元的紅包,固然若是不充值的話,也能夠抽獎,可是機率很是低,基本上是抽不到的,固然也有可能抽到的。

咱們下面能夠分析下代碼中的幾個字段值須要來判斷:

1. orderType(充值類型),若是值爲1的話,說明是充值500元的用戶,若是爲2的話,說明是充值200元的用戶,若是是3的話,說明是沒有充值的用戶。

2. isPay(是否已經成功充值了): 若是該值爲true的話,說明已經成功充值了,不然的話 說明沒有充值成功;就看成普通用戶來購買。

3. count(表示數量);普通用戶抽獎,若是數量有的話,就能夠拿到優惠卷,不然的話,不能拿到優惠卷。

複製代碼
// 咱們通常寫代碼以下處理操做
var order =  function(orderType,isPay,count) {
    if(orderType == 1) {  // 用戶充值500元到支付寶去
        if(isPay == true) { // 若是充值成功的話,100%中獎
            console.log("親愛的用戶,您中獎了100元紅包了");
        }else {
            // 充值失敗,就看成普通用戶來處理中獎信息
            if(count > 0) {
                console.log("親愛的用戶,您已抽到10元優惠卷");
            }else {
                console.log("親愛的用戶,請再接再礪哦");
            }
        }
    }else if(orderType == 2) {  // 用戶充值200元到支付寶去
        if(isPay == true) {     // 若是充值成功的話,100%中獎
            console.log("親愛的用戶,您中獎了20元紅包了");
        }else {
            // 充值失敗,就看成普通用戶來處理中獎信息
            if(count > 0) {
                console.log("親愛的用戶,您已抽到10元優惠卷");
            }else {
                console.log("親愛的用戶,請再接再礪哦");
            }
        }
    }else if(orderType == 3) {
        // 普通用戶來處理中獎信息
        if(count > 0) {
            console.log("親愛的用戶,您已抽到10元優惠卷");
        }else {
            console.log("親愛的用戶,請再接再礪哦");
        }
    }
};
複製代碼

上面的代碼雖然能夠實現需求,可是代碼不容易擴展且難以閱讀,假如之後我想一兩個條件,我想充值300元成功的話,能夠中獎150元紅包,那麼這時候又要改動裏面的代碼,這樣業務邏輯與代碼耦合性相對比較高,一不當心就改錯了代碼;這時候咱們試着使用職責鏈模式來依次傳遞對象來實現;

以下代碼:

複製代碼
function order500(orderType,isPay,count){
    if(orderType == 1 && isPay == true)    {
        console.log("親愛的用戶,您中獎了100元紅包了");
    }else {
        // 本身不處理,傳遞給下一個對象order200去處理
        order200(orderType,isPay,count);
    }
};
function order200(orderType,isPay,count) {
    if(orderType == 2 && isPay == true) {
        console.log("親愛的用戶,您中獎了20元紅包了");
    }else {
        // 本身不處理,傳遞給下一個對象普通用戶去處理
        orderNormal(orderType,isPay,count);
    }
};
function orderNormal(orderType,isPay,count){
    // 普通用戶來處理中獎信息
    if(count > 0) {
        console.log("親愛的用戶,您已抽到10元優惠卷");
    }else {
        console.log("親愛的用戶,請再接再礪哦");
    }
}
複製代碼

如上代碼咱們分別使用了三個函數order500,order200,orderNormal來分別處理本身的業務邏輯,若是目前的本身函數不能處理的事情,咱們傳遞給下面的函數去處理,依次類推,直到有一個函數能處理他,不然的話,該職責鏈模式直接從鏈中離開,告訴不能處理,拋出錯誤提示,上面的代碼雖然能夠看成職責鏈模式,可是咱們看上面的代碼能夠看到order500函數內依賴了order200這樣的函數,這樣就必須有這個函數,也違反了面向對象中的 開放-封閉原則。下面咱們繼續來理解編寫 靈活可拆分的職責鏈節點。

複製代碼
function order500(orderType,isPay,count){
    if(orderType == 1 && isPay == true)    {
        console.log("親愛的用戶,您中獎了100元紅包了");
    }else {
        //我不知道下一個節點是誰,反正把請求日後面傳遞
        return "nextSuccessor";
    }
};
function order200(orderType,isPay,count) {
    if(orderType == 2 && isPay == true) {
        console.log("親愛的用戶,您中獎了20元紅包了");
    }else {
        //我不知道下一個節點是誰,反正把請求日後面傳遞
        return "nextSuccessor";
    }
};
function orderNormal(orderType,isPay,count){
    // 普通用戶來處理中獎信息
    if(count > 0) {
        console.log("親愛的用戶,您已抽到10元優惠卷");
    }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;
}
//如今咱們把3個函數分別包裝成職責鏈節點:
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);  // 親愛的用戶,您中獎了100元紅包了
chainOrder500.passRequest(2,true,500);  // 親愛的用戶,您中獎了20元紅包了
chainOrder500.passRequest(3,true,500);  // 親愛的用戶,您已抽到10元優惠卷 
chainOrder500.passRequest(1,false,0);   // 親愛的用戶,請再接再礪哦
複製代碼

如上代碼;分別編寫order500,order200,orderNormal三個函數,在函數內分別處理本身的業務邏輯,若是本身的函數不能處理的話,就返回字符串nextSuccessor 日後面傳遞,而後封裝Chain這個構造函數,傳遞一個fn這個對象實列進來,且有本身的一個屬性successor,原型上有2個方法 setNextSuccessor 和 passRequest;setNextSuccessor 這個方法是指定節點在職責鏈中的順序的,把相對應的方法保存到this.successor這個屬性上,chainOrder500.setNextSuccessor(chainOrder200);chainOrder200.setNextSuccessor(chainOrderNormal);指定鏈中的順序,所以this.successor引用了order200這個方法和orderNormal這個方法,所以第一次chainOrder500.passRequest(1,true,500)調用的話,調用order500這個方法,直接輸出,第二次調用chainOrder500.passRequest(2,true,500);這個方法從鏈中首節點order500開始不符合,就返回successor字符串,而後this.successor && this.successor.passRequest.apply(this.successor,arguments);就執行這句代碼;上面咱們說過this.successor這個屬性引用了2個方法 分別爲order200和orderNormal,所以調用order200該方法,因此就返回了值,依次類推都是這個原理。那若是之後咱們想充值300元的紅包的話,咱們能夠編寫order300這個函數,而後實列一下鏈chain包裝起來,指定一下職責鏈中的順序便可,裏面的業務邏輯不須要作任何處理;

理解異步的職責鏈

上面的只是同步職責鏈,咱們讓每一個節點函數同步返回一個特定的值」nextSuccessor」,來表示是否把請求傳遞給下一個節點,在咱們開發中會常常碰到ajax異步請求,請求成功後,須要作某某事情,那麼這時候若是咱們再套用上面的同步請求的話,就不生效了,下面咱們來理解下使用異步的職責鏈來解決這個問題;咱們給Chain類再增長一個原型方法Chain.prototype.next,表示手動傳遞請求給職責鏈中的一下個節點。

以下代碼:

複製代碼
function Fn1() {
    console.log(1);
    return "nextSuccessor";
}
function Fn2() {
    console.log(2);
    var self = this;
    setTimeout(function(){
        self.next();
    },1000);
}
function Fn3() {
    console.log(3);
}
// 下面須要編寫職責鏈模式的封裝構造函數方法
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;
}
Chain.prototype.next = function(){
    return this.successor && this.successor.passRequest.apply(this.successor,arguments);
}
//如今咱們把3個函數分別包裝成職責鏈節點:
var chainFn1 = new Chain(Fn1);
var chainFn2 = new Chain(Fn2);
var chainFn3 = new Chain(Fn3);

// 而後指定節點在職責鏈中的順序
chainFn1.setNextSuccessor(chainFn2);
chainFn2.setNextSuccessor(chainFn3);

chainFn1.passRequest();  // 打印出1,2 過1秒後 會打印出3
複製代碼

調用函數 chainFn1.passRequest();後,會先執行發送者Fn1這個函數 打印出1,而後返回字符串 nextSuccessor;

 接着就執行return this.successor && this.successor.passRequest.apply(this.successor,arguments);這個函數到Fn2,打印2,接着裏面有一個setTimeout定時器異步函數,須要把請求給職責鏈中的下一個節點,所以過一秒後會打印出3;

職責鏈模式的優勢是:

 1. 解耦了請求發送者和N個接收者之間的複雜關係,不須要知道鏈中那個節點能處理你的請求,因此你

    只須要把請求傳遞到第一個節點便可。

 2. 鏈中的節點對象能夠靈活地拆分重組,增長或刪除一個節點,或者改變節點的位置都是很簡單的事情。

 3. 咱們還能夠手動指定節點的起始位置,並非說非得要從其實節點開始傳遞的.

 缺點:職責鏈模式中多了一點節點對象,可能在某一次請求過程當中,大部分節點沒有起到實質性做用,他們的做用只是讓

 請求傳遞下去,從性能方面考慮,避免過長的職責鏈提升性能。

六:命令模式的理解

 命令模式中的命令指的是一個執行某些特定事情的指令。

   命令模式使用的場景有:有時候須要向某些對象發送請求,可是並不知道請求的接收者是誰,也不知道請求的操做是什麼,此時但願用一種鬆耦合的方式來設計程序代碼;使得請求發送者和請求接受者消除彼此代碼中的耦合關係。

咱們先來列舉生活中的一個列子來講明下命令模式:好比咱們常常會在天貓上購買東西,而後下訂單,下單後我就想收到貨,而且但願貨物是真的,對於用戶來說它並關心下單後賣家怎麼發貨,固然賣家發貨也有時間的,好比24小時內發貨等,用戶更不關心快遞是給誰派送,固然有的人會關心是什麼快遞送貨的; 對於用戶來講,只要在規定的時間內發貨,且通常能在至關的時間內收到貨就能夠,固然命令模式也有撤銷命令和重作命令,好比咱們下單後,我忽然不想買了,我在發貨以前能夠取消訂單,也能夠從新下單(也就是重作命令);好比個人衣服尺碼拍錯了,我取消該訂單,從新拍一個大碼的。

1. 命令模式的列子

   記得我之前剛作前端的那會兒,也就是剛畢業進的第一家公司,進的是作外包項目的公司,該公司通常外包淘寶活動頁面及騰訊的遊戲頁面,咱們那會兒應該叫切頁面的前端,負責作一些html和css的工做,因此那會兒作騰訊的遊戲頁面,常常會幫他們作靜態頁面,好比在頁面放幾個按鈕,咱們只是按照設計稿幫騰訊遊戲哪方面的把樣式弄好,好比說頁面上的按鈕等事情,好比說具體說明的按鈕要怎麼操做,點擊按鈕後會發生什麼事情,咱們並不知道,咱們不知道他們的業務是什麼,固然咱們知道的確定會有點擊事件,具體要處理什麼業務咱們並不知道,這裏咱們就可使用命令模式來處理了:點擊按鈕以後,必須向某些負責具體行爲的對象發送請求,這些對象就是請求的接收者。可是目前咱們並不知道接收者是什麼對象,也不知道接受者究竟會作什麼事情,這時候咱們可使用命令模式來消除發送者與接收者的代碼耦合關係。

咱們先使用傳統的面向對象模式來設計代碼:

假設html結構以下:
<button id="button1">刷新菜單目錄</button>
<button id="button2">增長子菜單</button>
<button id="button3">刪除子菜單</button>

JS代碼以下:

複製代碼
var b1 = document.getElementById("button1"),
     b2 = document.getElementById("button2"),
     b3 = document.getElementById("button3");
     
 // 定義setCommand 函數,該函數負責往按鈕上面安裝命令。點擊按鈕後會執行command對象的execute()方法。
 var setCommand = function(button,command){
    button.onclick = function(){
        command.execute();
    }
 };
 // 下面咱們本身來定義各個對象來完成本身的業務操做
 var MenuBar = {
    refersh: function(){
        alert("刷新菜單目錄");
    }
 };
 var SubMenu = {
    add: function(){
        alert("增長子菜單");
    },
    del: function(){
        alert("刪除子菜單");
    }
 };
 // 下面是編寫命令類
 var RefreshMenuBarCommand = function(receiver){
    this.receiver = receiver;
 };
 RefreshMenuBarCommand.prototype.execute = function(){
    this.receiver.refersh();
 }
 // 增長命令操做
 var AddSubMenuCommand = function(receiver) {
    this.receiver = receiver;
 };
 AddSubMenuCommand.prototype.execute = function() {
    this.receiver.add();
 }
 // 刪除命令操做
 var DelSubMenuCommand = function(receiver) {
    this.receiver = receiver;
 };
 DelSubMenuCommand.prototype.execute = function(){
    this.receiver.del();
 }
 // 最後把命令接收者傳入到command對象中,而且把command對象安裝到button上面
 var refershBtn = new RefreshMenuBarCommand(MenuBar);
 var addBtn = new AddSubMenuCommand(SubMenu);
 var delBtn = new DelSubMenuCommand(SubMenu);
 
 setCommand(b1,refershBtn);
 setCommand(b2,addBtn);
 setCommand(b3,delBtn);
複製代碼

從上面的命令類代碼咱們能夠看到,任何一個操做都有一個execute這個方法來執行操做;上面的代碼是使用傳統的面向對象編程來實現命令模式的,命令模式過程式的請求調用封裝在command對象的execute方法裏。咱們有沒有發現上面的編寫代碼有點繁瑣呢,咱們可使用javascript中的回調函數來作這些事情的,在面向對象中,命令模式的接收者被當成command對象的屬性保存起來,同時約定執行命令的操做調用command.execute方法,可是若是咱們使用回調函數的話,那麼接收者被封閉在回調函數產生的環境中,執行操做將會更加簡單,僅僅執行回調函數便可,下面咱們來看看代碼以下:

代碼以下:

複製代碼
var setCommand = function(button,func) {
    button.onclick = function(){
        func();
    }
 }; 
 var MenuBar = {
    refersh: function(){
        alert("刷新菜單界面");
    }
 };
 var SubMenu = {
    add: function(){
        alert("增長菜單");
    }
 };
 // 刷新菜單
 var RefreshMenuBarCommand = function(receiver) {
    return function(){
        receiver.refersh();    
    };
 };
 // 增長菜單
 var AddSubMenuCommand = function(receiver) {
    return function(){
        receiver.add();    
    };
 };
 var refershMenuBarCommand = RefreshMenuBarCommand(MenuBar);
 // 增長菜單
 var addSubMenuCommand = AddSubMenuCommand(SubMenu);
 setCommand(b1,refershMenuBarCommand);
 
 setCommand(b2,addSubMenuCommand);
複製代碼

咱們還能夠以下使用javascript回調函數以下編碼:

複製代碼
// 以下代碼上的四個按鈕 點擊事件
var b1 = document.getElementById("button1"),
    b2 = document.getElementById("button2"),
    b3 = document.getElementById("button3"),
    b4 = document.getElementById("button4");
/*
 bindEnv函數負責往按鈕上面安裝點擊命令。點擊按鈕後,會調用
 函數
 */
var bindEnv = function(button,func) {
    button.onclick = function(){
        func();
    }
};
// 如今咱們來編寫具體處理業務邏輯代碼
var Todo1 = {
    test1: function(){
        alert("我是來作第一個測試的");
    }    
};
// 實現業務中的增刪改操做
var Menu = {
    add: function(){
        alert("我是來處理一些增長操做的");
    },
    del: function(){
        alert("我是來處理一些刪除操做的");
    },
    update: function(){
        alert("我是來處理一些更新操做的");
    }
};
// 調用函數
bindEnv(b1,Todo1.test1);
// 增長按鈕
bindEnv(b2,Menu.add);
// 刪除按鈕
bindEnv(b3,Menu.del);
// 更改按鈕
bindEnv(b4,Menu.update);
複製代碼

2. 理解宏命令:

   宏命令是一組命令的集合,經過執行宏命令的方式,能夠一次執行一批命令。

其實相似把頁面的全部函數方法放在一個數組裏面去,而後遍歷這個數組,依次

執行該方法的。

代碼以下:

複製代碼
var command1 = {
    execute: function(){
        console.log(1);
    }
}; 
var command2 = {
    execute: function(){
        console.log(2);
    }
};
var command3 = {
    execute: function(){
        console.log(3);
    }
};
// 定義宏命令,command.add方法把子命令添加進宏命令對象,
// 當調用宏命令對象的execute方法時,會迭代這一組命令對象,
// 而且依次執行他們的execute方法。
var command = function(){
    return {
        commandsList: [],
        add: function(command){
            this.commandsList.push(command);
        },
        execute: function(){
            for(var i = 0,commands = this.commandsList.length; i < commands; i+=1) {
                this.commandsList[i].execute();
            }
        }
    }
};
// 初始化宏命令
var c = command();
c.add(command1);
c.add(command2);
c.add(command3);
c.execute();  // 1,2,3
複製代碼

七:模板方法模式

    模板方法模式由二部分組成,第一部分是抽象父類,第二部分是具體實現的子類,通常的狀況下是抽象父類封裝了子類的算法框架,包括實現一些公共方法及封裝子類中全部方法的執行順序,子類能夠繼承這個父類,而且能夠在子類中重寫父類的方法,從而實現本身的業務邏輯。

好比說咱們要實現一個JS功能,好比表單驗證等js,那麼若是咱們沒有使用上一章講的使用javascript中的策略模式來解決表單驗證封裝代碼,而是本身寫的臨時表單驗證功能,確定是沒有進行任何封裝的,那麼這個時候咱們是針對兩個值是否相等給用戶彈出一個提示,若是再另一個頁面也有一個表單驗證,他們判斷的方式及業務邏輯基本相同的,只是比較的參數不一樣而已,咱們是否是又要考慮寫一個表單驗證代碼呢?那麼如今咱們能夠考慮使用模板方法模式來解決這個問題;公用的方法提取出來,不一樣的方法由具體的子類是實現。這樣設計代碼也可擴展性更強,代碼更優等優勢~

咱們不急着寫代碼,咱們能夠先來看一個列子,好比最近常常在qq羣裏面有不少前端招聘的信息,本身也接到不少公司或者獵頭問我是否須要找工做等電話,固然我如今是沒有打算找工做的,由於如今有更多的業餘時間能夠處理本身的事情,因此也以爲蠻不錯的~ 咱們先來看看招聘中面試這個流程;面試流程對於不少大型公司,好比BAT,面試過程其實很相似;所以咱們能夠總結面試過程當中以下:

1. 筆試:(不一樣的公司有不一樣的筆試題目)。

2. 技術面試(通常狀況下分爲二輪):第一輪面試你的有多是你將來直接主管或者將來同事問你前端的一些專業方面的技能及之前作過的項目,在項目中遇到哪些問題及當時是如何解決問題的,還有根據你的簡歷上的基本信息來交流的,好比說你簡歷說精通JS,那麼人家確定得問哦~ 第二輪面試通常都是公司的牛人或者架構師來問的,好比問你計算機基本原理,或者問一些數據結構與算法等信息;第二輪面試可能會更深刻的去了解你這我的的技術。

3. HR和總監或者總經理面試;那麼這一輪的話,HR可能會問下你一些我的基本信息等狀況,及問下你從此有什麼打算的我的規劃什麼的,總監或者總經理可能會問下你對他們的網站及產品有了解過沒有?及如今他們的產品有什麼問題,有沒有更好的建議或者如何改善的地方等信息;

4. 最後就是HR和你談薪資及通常幾個工做日能夠獲得通知,拿到offer(固然不符合的確定是沒有通知的哦);及本身有沒有須要瞭解公司的狀況等等信息;

通常的面試過程都是如上四點下來的,對於不一樣的公司都差很少的流程的,固然有些公司可能沒有上面的詳細流程的,我這邊這邊講通常的狀況下,好了,這邊就不扯了,這邊也不是講如何面試的哦,這邊只是經過這個列子讓咱們更加的理解javascript中模板方法模式;因此咱們如今回到正題上來;

咱們先來分析下上面的流程;咱們能夠總結以下:

首先咱們看一下百度的面試;所以咱們能夠先定義一個構造函數。

var BaiDuInterview = function(){};

那麼下面就有百度面試的流程哦~

1. 筆試

那麼咱們能夠封裝一個筆試的方法,代碼以下:

// baidu 筆試

BaiDuInterview.prototype.writtenTest = function(){

    console.log("我終於看到百度的筆試題了~");

};

2. 技術面試:

// 技術面試

BaiDuInterview.prototype.technicalInterview = function(){

    console.log("我是百度的技術負責人");

}; 

 3.  HR和總監或者總經理面試,咱們能夠稱之爲leader面試;代碼以下:

 // 領導面試

BaiDuInterview.prototype.leader = function(){

    console.log("百度leader來面試了");

};

4. HR談指望的薪資待遇及HR會告訴你何時會有通知,所以咱們這邊能夠稱之爲這個方法爲 是否拿到offer(固然不符合要求確定是沒有通知的哦)

// 等通知

BaiDuInterview.prototype.waitNotice = function(){

    console.log("百度的人力資源太不給力了,到如今都不給我通知");

};

如上看到代碼的基本結構,可是咱們還須要一個初始化方法;代碼以下:

// 代碼初始化

BaiDuInterview.prototype.init = function(){

    this.writtenTest();

    this.technicalInterview();

    this.leader();

    this.waitNotice();

};

var baiDuInterview = new BaiDuInterview();

baiDuInterview.init();

綜合所述:全部的代碼以下:

var BaiDuInterview = function(){};

 

// baidu 筆試

BaiDuInterview.prototype.writtenTest = function(){

    console.log("我終於看到百度的題目筆試題了~");

};

// 技術面試

BaiDuInterview.prototype.technicalInterview = function(){

    console.log("我是百度的技術負責人");

}; 

// 領導面試

BaiDuInterview.prototype.leader = function(){

    console.log("百度leader來面試了");

};

// 等通知

BaiDuInterview.prototype.waitNotice = function(){

    console.log("百度的人力資源太不給力了,到如今都不給我通知");

};

// 代碼初始化

BaiDuInterview.prototype.init = function(){

    this.writtenTest();

    this.technicalInterview();

    this.leader();

    this.waitNotice();

};

var baiDuInterview = new BaiDuInterview();

baiDuInterview.init();

 

上面咱們能夠看到百度面試的基本流程如上面的代碼,那麼阿里和騰訊的也和上面的代碼相似(這裏就不一一貼同樣的代碼哦),所以咱們能夠把公用代碼提取出來;咱們首先定義一個類,叫面試Interview

那麼代碼改爲以下:

var Interview = function(){};

1. 筆試:

我無論你是百度的筆試仍是阿里或者騰訊的筆試題,我這邊統稱爲筆試(WrittenTest),那麼大家公司有不一樣的筆試題,都交給子類去具體實現,父類方法無論具體如何實現,筆試題具體是什麼樣的 我都無論。代碼變爲以下:

// 筆試

Interview.prototype.writtenTest = function(){

    console.log("我終於看到筆試題了~");

};

2. 技術面試,技術面試原理也同樣,這裏就很少說,直接貼代碼:

// 技術面試

Interview.prototype.technicalInterview = function(){

    console.log("我是技術負責人負責技術面試");

}; 

3. 領導面試

// 領導面試

Interview.prototype.leader = function(){

    console.log("leader來面試了");

};

4. 等通知

// 等通知

Interview.prototype.waitNotice = function(){

    console.log("人力資源太不給力了,到如今都不給我通知");

};

代碼初始化方法以下:

// 代碼初始化

Interview.prototype.init = function(){

    this.writtenTest();

    this.technicalInterview();

    this.leader();

    this.waitNotice();

};

二:建立子類

如今咱們來建立一個百度的子類來繼承上面的父類;代碼以下:

var BaiDuInterview = function(){};

BaiDuInterview.prototype = new Interview();

如今咱們能夠在子類BaiDuInterview 重寫父類Interview中的方法;代碼以下:

// 子類重寫方法 實現本身的業務邏輯

BaiDuInterview.prototype.writtenTest = function(){

    console.log("我終於看到百度的筆試題了");

}

BaiDuInterview.prototype.technicalInterview = function(){

    console.log("我是百度的技術負責人,想面試找我");

}

BaiDuInterview.prototype.leader = function(){

    console.log("我是百度的leader,不想加班的或者業績提不上去的給我滾蛋");

}

BaiDuInterview.prototype.waitNotice = function(){

    console.log("百度的人力資源太不給力了,我等的花兒都謝了!!");

}

var baiDuInterview = new BaiDuInterview();

baiDuInterview.init();

如上看到,咱們直接調用子類baiDuInterview.init()方法,因爲咱們子類baiDuInterview沒有init方法,可是它繼承了父類,因此會到父類中查找對應的init方法;因此會迎着原型鏈到父類中查找;對於其餘子類,好比阿里類代碼也是同樣的,這裏就很少介紹了,對於父類這個方法 Interview.prototype.init() 是模板方法,由於他封裝了子類中算法框架,它做爲一個算法的模板,指導子類以什麼樣的順序去執行代碼。

三: Javascript中的模板模式使用場景

雖然在java中也有子類實現父類的接口,可是我認爲javascript中能夠和java中不一樣的,java中可能父類就是一個空的類,子類去實現這個父類的接口,在javascript中我認爲徹底把公用的代碼寫在父函數內,若是未來業務邏輯須要更改的話,或者說添加新的業務邏輯,咱們徹底可使用子類去重寫這個父類,這樣的話代碼可擴展性強,更容易維護。因爲本人不是專業java的,因此描述java中的知識點有誤的話,請理解~~

八:理解javascript中的策略模式

1. 理解javascript中的策略模式

策略模式的定義是:定義一系列的算法,把它們一個個封裝起來,而且使它們能夠相互替換。

使用策略模式的優勢以下:

優勢:1. 策略模式利用組合,委託等技術和思想,有效的避免不少if條件語句。

      2. 策略模式提供了開放-封閉原則,使代碼更容易理解和擴展。

      3. 策略模式中的代碼能夠複用。

一:使用策略模式計算獎金;

下面的demo是我在書上看到的,可是沒有關係,咱們只是來理解下策略模式的使用而已,咱們可使用策略模式來計算獎金問題;

好比公司的年終獎是根據員工的工資和績效來考覈的,績效爲A的人,年終獎爲工資的4倍,績效爲B的人,年終獎爲工資的3倍,績效爲C的人,年終獎爲工資的2倍;如今咱們使用通常的編碼方式會以下這樣編寫代碼:

複製代碼
var calculateBouns = function(salary,level) {
    if(level === 'A') {
        return salary * 4;
    }
    if(level === 'B') {
        return salary * 3;
    }
    if(level === 'C') {
        return salary * 2;
    }
};
// 調用以下:
console.log(calculateBouns(4000,'A')); // 16000
console.log(calculateBouns(2500,'B')); // 7500
複製代碼

第一個參數爲薪資,第二個參數爲等級;

代碼缺點以下:

calculateBouns 函數包含了不少if-else語句。

calculateBouns 函數缺少彈性,假如還有D等級的話,那麼咱們須要在calculateBouns 函數內添加判斷等級D的if語句;

算法複用性差,若是在其餘的地方也有相似這樣的算法的話,可是規則不同,咱們這些代碼不能通用。

2. 使用組合函數重構代碼

組合函數是把各類算法封裝到一個個的小函數裏面,好比等級A的話,封裝一個小函數,等級爲B的話,也封裝一個小函數,以此類推;以下代碼:

複製代碼
var performanceA = function(salary) {
    return salary * 4;
};
var performanceB = function(salary) {
    return salary * 3;
};
        
var performanceC = function(salary) {
    return salary * 2
};
var calculateBouns = function(level,salary) {
    if(level === 'A') {
        return performanceA(salary);
    }
    if(level === 'B') {
        return performanceB(salary);
    }
    if(level === 'C') {
        return performanceC(salary);
    }
};
// 調用以下
console.log(calculateBouns('A',4500)); // 18000
複製代碼

代碼看起來有點改善,可是仍是有以下缺點:

calculateBouns 函數有可能會愈來愈大,好比增長D等級的時候,並且缺少彈性。

3. 使用策略模式重構代碼

策略模式指的是 定義一系列的算法,把它們一個個封裝起來,將不變的部分和變化的部分隔開,實際就是將算法的使用和實現分離出來;算法的使用方式是不變的,都是根據某個算法取得計算後的獎金數,而算法的實現是根據績效對應不一樣的績效規則;

一個基於策略模式的程序至少由2部分組成,第一個部分是一組策略類,策略類封裝了具體的算法,並負責具體的計算過程。第二個部分是環境類Context,該Context接收客戶端的請求,隨後把請求委託給某一個策略類。咱們先使用傳統面向對象來實現;

以下代碼:

複製代碼
var performanceA = function(){};
performanceA.prototype.calculate = function(salary) {
    return salary * 4;
};      
var performanceB = function(){};
performanceB.prototype.calculate = function(salary) {
    return salary * 3;
};
var performanceC = function(){};
performanceC.prototype.calculate = function(salary) {
    return salary * 2;
};
// 獎金類
var Bouns = function(){
    this.salary = null;    // 原始工資
    this.levelObj = null;  // 績效等級對應的策略對象
};
Bouns.prototype.setSalary = function(salary) {
    this.salary = salary;  // 保存員工的原始工資
};
Bouns.prototype.setlevelObj = function(levelObj){
    this.levelObj = levelObj;  // 設置員工績效等級對應的策略對象
};
// 取得獎金數
Bouns.prototype.getBouns = function(){
    // 把計算獎金的操做委託給對應的策略對象
    return this.levelObj.calculate(this.salary);
};
var bouns = new Bouns();
bouns.setSalary(10000);
bouns.setlevelObj(new performanceA()); // 設置策略對象
console.log(bouns.getBouns());  // 40000
       
bouns.setlevelObj(new performanceB()); // 設置策略對象
console.log(bouns.getBouns());  // 30000
複製代碼

如上代碼使用策略模式重構代碼,能夠看到代碼職責更新分明,代碼變得更加清晰。

4. Javascript版本的策略模式

複製代碼
//代碼以下:
var obj = {
        "A": function(salary) {
            return salary * 4;
        },
        "B" : function(salary) {
            return salary * 3;
        },
        "C" : function(salary) {
            return salary * 2;
        } 
};
var calculateBouns =function(level,salary) {
    return obj[level](salary);
};
console.log(calculateBouns('A',10000)); // 40000
複製代碼

能夠看到代碼更加簡單明瞭;

策略模式指的是定義一系列的算法,而且把它們封裝起來,可是策略模式不只僅只封裝算法,咱們還能夠對用來封裝一系列的業務規則,只要這些業務規則目標一致,咱們就可使用策略模式來封裝它們;

表單效驗

好比咱們常常來進行表單驗證,好比註冊登陸對話框,咱們登陸以前要進行驗證操做:好比有如下幾條邏輯:

用戶名不能爲空

密碼長度不能小於6位。

手機號碼必須符合格式。

好比HTML代碼以下:

複製代碼
<form action = "http://www.baidu.com" id="registerForm" method = "post">
        <p>
            <label>請輸入用戶名:</label>
            <input type="text" name="userName"/>
        </p>
        <p>
            <label>請輸入密碼:</label>
            <input type="text" name="password"/>
        </p>
        <p>
            <label>請輸入手機號碼:</label>
            <input type="text" name="phoneNumber"/>
        </p>
</form>
複製代碼

咱們正常的編寫表單驗證代碼以下:

複製代碼
var registerForm = document.getElementById("registerForm");
registerForm.onsubmit = function(){
    if(registerForm.userName.value === '') {
        alert('用戶名不能爲空');
        return;
    }
    if(registerForm.password.value.length < 6) {
        alert("密碼的長度不能小於6位");
        return;
    }
    if(!/(^1[3|5|8][0-9]{9}$)/.test(registerForm.phoneNumber.value)) {
        alert("手機號碼格式不正確");
        return;
    }
}
複製代碼

可是這樣編寫代碼有以下缺點:

1.registerForm.onsubmit 函數比較大,代碼中包含了不少if語句;

2.registerForm.onsubmit 函數缺少彈性,若是增長了一種新的效驗規則,或者想把密碼的長度效驗從6改爲8,咱們必須改registerForm.onsubmit 函數內部的代碼。違反了開放-封閉原則。

3. 算法的複用性差,若是在程序中增長了另一個表單,這個表單也須要進行一些相似的效驗,那麼咱們可能又須要複製代碼了;

下面咱們可使用策略模式來重構表單效驗;

第一步咱們先來封裝策略對象;以下代碼:

複製代碼
var strategy = {
    isNotEmpty: function(value,errorMsg) {
        if(value === '') {
            return errorMsg;
        }
    },
    // 限制最小長度
    minLength: function(value,length,errorMsg) {
        if(value.length < length) {
            return errorMsg;
        }
    },
    // 手機號碼格式
    mobileFormat: function(value,errorMsg) {
        if(!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
            return errorMsg;
        }
    } 
};
複製代碼

接下來咱們準備實現Validator類,Validator類在這裏做爲Context,負責接收用戶的請求並委託給strategy 對象,以下代碼:

複製代碼
var Validator = function(){
    this.cache = [];  // 保存效驗規則
};
Validator.prototype.add = function(dom,rule,errorMsg) {
    var str = rule.split(":");
    this.cache.push(function(){
        // str 返回的是 minLength:6 
        var strategy = str.shift();
        str.unshift(dom.value); // 把input的value添加進參數列表
        str.push(errorMsg);  // 把errorMsg添加進參數列表
        return strategys[strategy].apply(dom,str);
    });
};
Validator.prototype.start = function(){
    for(var i = 0, validatorFunc; validatorFunc = this.cache[i++]; ) {
        var msg = validatorFunc(); // 開始效驗 並取得效驗後的返回信息
        if(msg) {
            return msg;
        }
    }
};
複製代碼

Validator類在這裏做爲Context,負責接收用戶的請求並委託給strategys對象。上面的代碼中,咱們先建立一個Validator對象,而後經過validator.add方法往validator對象中添加一些效驗規則,validator.add方法接收3個參數,以下代碼:

validator.add(registerForm.password,'minLength:6','密碼長度不能小於6位');

registerForm.password 爲效驗的input輸入框dom節點;

minLength:6: 是以一個冒號隔開的字符串,冒號前面的minLength表明客戶挑選的strategys對象,冒號後面的數字6表示在效驗過程當中所必須驗證的參數,minLength:6的意思是效驗 registerForm.password 這個文本輸入框的value最小長度爲6位;若是字符串中不包含冒號,說明效驗過程當中不須要額外的效驗信息;

第三個參數是當效驗未經過時返回的錯誤信息;

當咱們往validator對象裏添加完一系列的效驗規則以後,會調用validator.start()方法來啓動效驗。若是validator.start()返回了一個errorMsg字符串做爲返回值,說明該次效驗沒有經過,此時須要registerForm.onsubmit方法返回false來阻止表單提交。下面咱們來看看初始化代碼以下:

複製代碼
var validateFunc = function(){
    var validator = new Validator(); // 建立一個Validator對象
    /* 添加一些效驗規則 */
    validator.add(registerForm.userName,'isNotEmpty','用戶名不能爲空');
    validator.add(registerForm.password,'minLength:6','密碼長度不能小於6位');
    validator.add(registerForm.userName,'mobileFormat','手機號碼格式不正確');

    var errorMsg = validator.start(); // 得到效驗結果
    return errorMsg; // 返回效驗結果
};
var registerForm = document.getElementById("registerForm");
registerForm.onsubmit = function(){
    var errorMsg = validateFunc();
    if(errorMsg){
        alert(errorMsg);
        return false;
    }
}
複製代碼

下面是全部的代碼以下:

複製代碼
var strategys = {
    isNotEmpty: function(value,errorMsg) {
        if(value === '') {
            return errorMsg;
        }
    },
    // 限制最小長度
    minLength: function(value,length,errorMsg) {
        if(value.length < length) {
            return errorMsg;
        }
    },
    // 手機號碼格式
    mobileFormat: function(value,errorMsg) {
        if(!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
            return errorMsg;
        }
    } 
};
var Validator = function(){
    this.cache = [];  // 保存效驗規則
};
Validator.prototype.add = function(dom,rule,errorMsg) {
    var str = rule.split(":");
    this.cache.push(function(){
        // str 返回的是 minLength:6 
        var strategy = str.shift();
        str.unshift(dom.value); // 把input的value添加進參數列表
        str.push(errorMsg);  // 把errorMsg添加進參數列表
        return strategys[strategy].apply(dom,str);
    });
};
Validator.prototype.start = function(){
    for(var i = 0, validatorFunc; validatorFunc = this.cache[i++]; ) {
        var msg = validatorFunc(); // 開始效驗 並取得效驗後的返回信息
        if(msg) {
            return msg;
        }
    }
};

var validateFunc = function(){
    var validator = new Validator(); // 建立一個Validator對象
    /* 添加一些效驗規則 */
    validator.add(registerForm.userName,'isNotEmpty','用戶名不能爲空');
    validator.add(registerForm.password,'minLength:6','密碼長度不能小於6位');
    validator.add(registerForm.userName,'mobileFormat','手機號碼格式不正確');

    var errorMsg = validator.start(); // 得到效驗結果
    return errorMsg; // 返回效驗結果
};
var registerForm = document.getElementById("registerForm");
registerForm.onsubmit = function(){
    var errorMsg = validateFunc();
    if(errorMsg){
        alert(errorMsg);
        return false;
    }
};
複製代碼

如上使用策略模式來編寫表單驗證代碼能夠看到好處了,咱們經過add配置的方式就完成了一個表單的效驗;這樣的話,那麼代碼能夠當作一個組件來使用,而且能夠隨時調用,在修改表單驗證規則的時候,也很是方便,經過傳遞參數便可調用;

給某個文本輸入框添加多種效驗規則,上面的代碼咱們能夠看到,咱們只是給輸入框只能對應一種效驗規則,好比上面的咱們只能效驗輸入框是否爲空,validator.add(registerForm.userName,'isNotEmpty','用戶名不能爲空');可是若是咱們既要效驗輸入框是否爲空,還要效驗輸入框的長度不要小於10位的話,那麼咱們指望須要像以下傳遞參數:

validator.add(registerForm.userName,[{strategy:’isNotEmpty’,errorMsg:’用戶名不能爲空’},{strategy: 'minLength:6',errorMsg:'用戶名長度不能小於6位'}])

咱們能夠編寫代碼以下:

複製代碼
// 策略對象
var strategys = {
    isNotEmpty: function(value,errorMsg) {
        if(value === '') {
            return errorMsg;
        }
    },
    // 限制最小長度
    minLength: function(value,length,errorMsg) {
        if(value.length < length) {
            return errorMsg;
        }
    },
    // 手機號碼格式
    mobileFormat: function(value,errorMsg) {
        if(!/(^1[3|5|8][0-9]{9}$)/.test(value)) {
            return errorMsg;
        }
    } 
};
var Validator = function(){
    this.cache = [];  // 保存效驗規則
};
Validator.prototype.add = function(dom,rules) {
    var self = this;
    for(var i = 0, rule; rule = rules[i++]; ){
        (function(rule){
            var strategyAry = rule.strategy.split(":");
            var errorMsg = rule.errorMsg;
            self.cache.push(function(){
                var strategy = strategyAry.shift();
                strategyAry.unshift(dom.value);
                strategyAry.push(errorMsg);
                return strategys[strategy].apply(dom,strategyAry);
            });
        })(rule);
    }
};
Validator.prototype.start = function(){
    for(var i = 0, validatorFunc; validatorFunc = this.cache[i++]; ) {
    var msg = validatorFunc(); // 開始效驗 並取得效驗後的返回信息
    if(msg) {
        return msg;
    }
    }
};
// 代碼調用
var registerForm = document.getElementById("registerForm");
var validateFunc = function(){
    var validator = new Validator(); // 建立一個Validator對象
    /* 添加一些效驗規則 */
    validator.add(registerForm.userName,[
        {strategy: 'isNotEmpty',errorMsg:'用戶名不能爲空'},
        {strategy: 'minLength:6',errorMsg:'用戶名長度不能小於6位'}
    ]);
    validator.add(registerForm.password,[
        {strategy: 'minLength:6',errorMsg:'密碼長度不能小於6位'},
    ]);
    validator.add(registerForm.phoneNumber,[
        {strategy: 'mobileFormat',errorMsg:'手機號格式不正確'},
    ]);
    var errorMsg = validator.start(); // 得到效驗結果
    return errorMsg; // 返回效驗結果
};
// 點擊肯定提交
registerForm.onsubmit = function(){
    var errorMsg = validateFunc();
    if(errorMsg){
        alert(errorMsg);
        return false;
    }
}
複製代碼

注意:如上代碼都是按照書上來作的,都是看到書的代碼,最主要咱們理解策略模式實現,好比上面的表單驗證功能是這樣封裝的代碼,咱們平時使用jquery插件表單驗證代碼原來是這樣封裝的,爲此咱們之後也可使用這種方式來封裝表單等學習;

九:Javascript中理解發布--訂閱模式

1. 發佈訂閱模式介紹

   發佈---訂閱模式又叫觀察者模式,它定義了對象間的一種一對多的關係,讓多個觀察者對象同時監聽某一個主題對象,當一個對象發生改變時,全部依賴於它的對象都將獲得通知。

  現實生活中的發佈-訂閱模式;

好比小紅最近在淘寶網上看上一雙鞋子,可是呢 聯繫到賣家後,才發現這雙鞋賣光了,可是小紅對這雙鞋又很是喜歡,因此呢聯繫賣家,問賣家何時有貨,賣家告訴她,要等一個星期後纔有貨,賣家告訴小紅,要是你喜歡的話,你能夠收藏咱們的店鋪,等有貨的時候再通知你,因此小紅收藏了此店鋪,但與此同時,小明,小花等也喜歡這雙鞋,也收藏了該店鋪;等來貨的時候就依次會通知他們;

在上面的故事中,能夠看出是一個典型的發佈訂閱模式,賣家是屬於發佈者,小紅,小明等屬於訂閱者,訂閱該店鋪,賣家做爲發佈者,當鞋子到了的時候,會依次通知小明,小紅等,依次使用旺旺等工具給他們發佈消息;

發佈訂閱模式的優勢:

  1. 支持簡單的廣播通訊,當對象狀態發生改變時,會自動通知已經訂閱過的對象。

好比上面的列子,小明,小紅不須要每天逛淘寶網看鞋子到了沒有,在合適的時間點,發佈者(賣家)來貨了的時候,會通知該訂閱者(小紅,小明等人)。

  2. 發佈者與訂閱者耦合性下降,發佈者只管發佈一條消息出去,它不關心這條消息如何被訂閱者使用,同時,訂閱者只監聽發佈者的事件名,只要發佈者的事件名不變,它無論發佈者如何改變;同理賣家(發佈者)它只須要將鞋子來貨的這件事告訴訂閱者(買家),他無論買家到底買仍是不買,仍是買其餘賣家的。只要鞋子到貨了就通知訂閱者便可。

 對於第一點,咱們平常工做中也常用到,好比咱們的ajax請求,請求有成功(success)和失敗(error)的回調函數,咱們能夠訂閱ajax的success和error事件。咱們並不關心對象在異步運行的狀態,咱們只關心success的時候或者error的時候咱們要作點咱們本身的事情就能夠了~

發佈訂閱模式的缺點:

  建立訂閱者須要消耗必定的時間和內存。

  雖然能夠弱化對象之間的聯繫,若是過分使用的話,反而使代碼很差理解及代碼很差維護等等。

2. 如何實現發佈--訂閱模式?

   1. 首先要想好誰是發佈者(好比上面的賣家)。

   2. 而後給發佈者添加一個緩存列表,用於存放回調函數來通知訂閱者(好比上面的買家收藏了賣家的店鋪,賣家經過收藏了該店鋪的一個列表名單)。

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

咱們還能夠在回調函數裏面添加一點參數,好比鞋子的顏色,鞋子尺碼等信息;

咱們先來實現下簡單的發佈-訂閱模式;代碼以下:

複製代碼
var shoeObj = {}; // 定義發佈者
shoeObj.list = []; // 緩存列表 存放訂閱者回調函數
        
// 增長訂閱者
shoeObj.listen = function(fn) {
    shoeObj.list.push(fn);  // 訂閱消息添加到緩存列表
}

// 發佈消息
shoeObj.trigger = function(){
    for(var i = 0,fn; fn = this.list[i++];) {
        fn.apply(this,arguments); 
    }
}
// 小紅訂閱以下消息
shoeObj.listen(function(color,size){
    console.log("顏色是:"+color);
    console.log("尺碼是:"+size);  
});

// 小花訂閱以下消息
shoeObj.listen(function(color,size){
    console.log("再次打印顏色是:"+color);
    console.log("再次打印尺碼是:"+size); 
});
shoeObj.trigger("紅色",40);
shoeObj.trigger("黑色",42);
複製代碼

運行結果以下:

打印如上截圖,咱們看到訂閱者接收到發佈者的每一個消息,可是呢,對於小紅來講,她只想接收顏色爲紅色的消息,不想接收顏色爲黑色的消息,爲此咱們須要對代碼進行以下改造下,咱們能夠先增長一個key,使訂閱者只訂閱本身感興趣的消息。代碼以下:

複製代碼
var shoeObj = {}; // 定義發佈者
shoeObj.list = []; // 緩存列表 存放訂閱者回調函數
        
// 增長訂閱者
shoeObj.listen = function(key,fn) {
    if(!this.list[key]) {
        // 若是尚未訂閱過此類消息,給該類消息建立一個緩存列表
        this.list[key] = []; 
    }
    this.list[key].push(fn);  // 訂閱消息添加到緩存列表
}

// 發佈消息
shoeObj.trigger = function(){
    var key = Array.prototype.shift.call(arguments); // 取出消息類型名稱
    var fns = this.list[key];  // 取出該消息對應的回調函數的集合

    // 若是沒有訂閱過該消息的話,則返回
    if(!fns || fns.length === 0) {
        return;
    }
    for(var i = 0,fn; fn = fns[i++]; ) {
        fn.apply(this,arguments); // arguments 是發佈消息時附送的參數
    }
};

// 小紅訂閱以下消息
shoeObj.listen('red',function(size){
    console.log("尺碼是:"+size);  
});

// 小花訂閱以下消息
shoeObj.listen('block',function(size){
    console.log("再次打印尺碼是:"+size); 
});
shoeObj.trigger("red",40);
shoeObj.trigger("block",42);
複製代碼

上面的代碼,咱們再來運行打印下 以下:

能夠看到,訂閱者只訂閱本身感興趣的消息了;

3. 發佈---訂閱模式的代碼封裝

咱們知道,對於上面的代碼,小紅去買鞋這麼一個對象shoeObj 進行訂閱,可是若是之後咱們須要對買房子或者其餘的對象進行訂閱呢,咱們須要複製上面的代碼,再從新改下里面的對象代碼;爲此咱們須要進行代碼封裝;

以下代碼封裝:

複製代碼
var event = {
    list: [],
    listen: function(key,fn) {
        if(!this.list[key]) {
            this.list[key] = [];
        }
        // 訂閱的消息添加到緩存列表中
        this.list[key].push(fn);
    },
    trigger: function(){
        var key = Array.prototype.shift.call(arguments);
        var fns = this.list[key];
        // 若是沒有訂閱過該消息的話,則返回
        if(!fns || fns.length === 0) {
            return;
        }
        for(var i = 0,fn; fn = fns[i++];) {
            fn.apply(this,arguments);
        }
    }
};
複製代碼

咱們再定義一個initEvent函數,這個函數使全部的普通對象都具備發佈訂閱功能,以下代碼:

複製代碼
var initEvent = function(obj) {
    for(var i in event) {
        obj[i] = event[i];
    }
};
// 咱們再來測試下,咱們仍是給shoeObj這個對象添加發布-訂閱功能;
var shoeObj = {};
initEvent(shoeObj);

// 小紅訂閱以下消息
shoeObj.listen('red',function(size){
    console.log("尺碼是:"+size);  
});

// 小花訂閱以下消息
shoeObj.listen('block',function(size){
    console.log("再次打印尺碼是:"+size); 
});
shoeObj.trigger("red",40);
shoeObj.trigger("block",42);
複製代碼

4. 如何取消訂閱事件?

好比上面的列子,小紅她忽然不想買鞋子了,那麼對於賣家的店鋪他不想再接受該店鋪的消息,那麼小紅能夠取消該店鋪的訂閱。

以下代碼:

複製代碼
event.remove = function(key,fn){
    var fns = this.list[key];
    // 若是key對應的消息沒有訂閱過的話,則返回
    if(!fns) {
        return false;
    }
    // 若是沒有傳入具體的回調函數,表示須要取消key對應消息的全部訂閱
    if(!fn) {
        fn && (fns.length = 0);
    }else {
        for(var i = fns.length - 1; i >= 0; i--) {
            var _fn = fns[i];
            if(_fn === fn) {
                fns.splice(i,1); // 刪除訂閱者的回調函數
            }
        }
    }
};
// 測試代碼以下:
var initEvent = function(obj) {
    for(var i in event) {
        obj[i] = event[i];
    }
};
var shoeObj = {};
initEvent(shoeObj);

// 小紅訂閱以下消息
shoeObj.listen('red',fn1 = function(size){
    console.log("尺碼是:"+size);  
});

// 小花訂閱以下消息
shoeObj.listen('red',fn2 = function(size){
    console.log("再次打印尺碼是:"+size); 
});
shoeObj.remove("red",fn1);
shoeObj.trigger("red",42);
複製代碼

運行結果以下:

5. 全局--發佈訂閱對象代碼封裝

咱們再來看看咱們傳統的ajax請求吧,好比咱們傳統的ajax請求,請求成功後須要作以下事情:

 1. 渲染數據。

 2. 使用數據來作一個動畫。

那麼咱們之前確定是以下寫代碼:

$.ajax(「http://127.0.0.1/index.php」,function(data){
    rendedData(data);  // 渲染數據
    doAnimate(data);  // 實現動畫 
});

假如之後還須要作點事情的話,咱們還須要在裏面寫調用的方法;這樣代碼就耦合性很高,那麼咱們如今使用發佈-訂閱模式來看如何重構上面的業務需求代碼;

複製代碼
$.ajax(「http://127.0.0.1/index.php」,function(data){
    Obj.trigger(‘success’,data);  // 發佈請求成功後的消息
});
// 下面咱們來訂閱此消息,好比我如今訂閱渲染數據這個消息;
Obj.listen(「success」,function(data){
   renderData(data);
});
// 訂閱動畫這個消息
Obj.listen(「success」,function(data){
   doAnimate(data); 
});
複製代碼

爲此咱們能夠封裝一個全局發佈-訂閱模式對象;以下代碼:

複製代碼
var Event = (function(){
    var list = {},
          listen,
          trigger,
          remove;
          listen = function(key,fn){
            if(!list[key]) {
                list[key] = [];
            }
            list[key].push(fn);
        };
        trigger = function(){
            var key = Array.prototype.shift.call(arguments),
                 fns = list[key];
            if(!fns || fns.length === 0) {
                return false;
            }
            for(var i = 0, fn; fn = fns[i++];) {
                fn.apply(this,arguments);
            }
        };
        remove = function(key,fn){
            var fns = list[key];
            if(!fns) {
                return false;
            }
            if(!fn) {
                fns && (fns.length = 0);
            }else {
                for(var i = fns.length - 1; i >= 0; i--){
                    var _fn = fns[i];
                    if(_fn === fn) {
                        fns.splice(i,1);
                    }
                }
            }
        };
        return {
            listen: listen,
            trigger: trigger,
            remove: remove
        }
})();
// 測試代碼以下:
Event.listen("color",function(size) {
    console.log("尺碼爲:"+size); // 打印出尺碼爲42
});
Event.trigger("color",42);
複製代碼

6. 理解模塊間通訊

咱們使用上面封裝的全局的發佈-訂閱對象來實現兩個模塊之間的通訊問題;好比如今有一個頁面有一個按鈕,每次點擊此按鈕後,div中會顯示此按鈕被點擊的總次數;以下代碼:

<button id="count">點將我</button>

<div id="showcount"></div>

咱們中的a.js 負責處理點擊操做 及發佈消息;以下JS代碼:

複製代碼
var a = (function(){
    var count = 0;
    var button = document.getElementById("count");
    button.onclick = function(){
        Event.trigger("add",count++);
    }
})();
複製代碼

b.js 負責監聽add這個消息,並把點擊的總次數顯示到頁面上來;以下代碼:

複製代碼
var b = (function(){
    var div = document.getElementById("showcount");
    Event.listen('add',function(count){
        div.innerHTML = count;
    });
})();
複製代碼

下面是html代碼以下,JS應用以下引用便可:

複製代碼
<!doctype html>
<html lang="en">
 <head>
  <meta charset="UTF-8">
  <title>Document</title>
  <script src="global.js"></script>
 </head>
 <body>
    <button id="count">點將我</button>
    <div id="showcount"></div>
    <script src = "a.js"></script>
    <script src = "b.js"></script>
 </body>
</html>
複製代碼

如上代碼,當點擊一次按鈕後,showcount的div會自動加1,如上演示的是2個模塊之間如何使用發佈-訂閱模式之間的通訊問題;

其中global.js 就是咱們上面封裝的全局-發佈訂閱模式對象的封裝代碼;

十:理解中介者模式

    先來理解這麼一個問題,假如咱們前端開發接的需求是需求方給咱們需求,可能一個前端開發會和多個需求方打交道,因此會保持多個需求方的聯繫,那麼在程序裏面就意味着保持多個對象的引用,當程序的規模越大,對象會愈來愈多,他們之間的關係會愈來愈複雜,那如今假如如今有一箇中介者(假如就是咱們的主管)來對接多個需求方的需求,那麼需求方只須要把全部的需求給咱們主管就能夠,主管會依次看咱們的工做量來給咱們分配任務,這樣的話,咱們前端開發就不須要和多個業務方聯繫,咱們只須要和咱們主管(也就是中介)聯繫便可,這樣的好處就弱化了對象之間的耦合。

平常生活中的列子:

    中介者模式對於咱們平常生活中常常會碰到,好比咱們去房屋中介去租房,房屋中介人在租房者和房東出租者之間造成一條中介;租房者並不關心租誰的房,房東出租者也並不關心它租給誰,由於有中介,因此須要中介來完成這場交易。

中介者模式的做用是解除對象與對象之間的耦合關係,增長一箇中介對象後,全部的相關對象都經過中介者對象來通訊,而不是相互引用,因此當一個對象發送改變時,只須要通知中介者對象便可。中介者使各個對象之間耦合鬆散,並且能夠獨立地改變它們之間的交互。

實現中介者的列子以下:

不知道你們有沒有玩過英雄殺這個遊戲,最先的時候,英雄殺有2我的(分別是敵人和本身);咱們針對這個遊戲先使用普通的函數來實現以下:

好比先定義一個函數,該函數有三個方法,分別是win(贏), lose(輸),和die(敵人死亡)這三個函數;只要一個玩家死亡該遊戲就結束了,同時須要通知它的對手勝利了; 代碼須要編寫以下:

複製代碼
function Hero(name) {
    this.name = name;
    this.enemy = null; 
}
Hero.prototype.win = function(){
    console.log(this.name + 'Won');
}
Hero.prototype.lose = function(){
    console.log(this.name + 'lose');
}
Hero.prototype.die = function(){
    this.lose();
    this.enemy.win();
}
// 初始化2個對象
var h1 = new Hero("朱元璋");
var h2 = new Hero("劉伯溫");
// 給玩家設置敵人
h1.enemy = h2;
h2.enemy = h1;
// 朱元璋死了 也就輸了
h1.die();  // 輸出 朱元璋lose 劉伯溫Won
複製代碼

如今咱們再來爲遊戲添加隊友

好比如今咱們來爲遊戲添加隊友,好比英雄殺有6人一組,那麼這種狀況下就有隊友,敵人也有3個;所以咱們須要區分是敵人仍是隊友須要隊的顏色這個字段,若是隊的顏色相同的話,那麼就是同一個隊的,不然的話就是敵人;

咱們能夠先定義一個數組players來保存全部的玩家,在建立玩家以後,循環players來給每一個玩家設置隊友或者敵人;

var players = [];

接着咱們再來編寫Hero這個函數;代碼以下:

複製代碼
var players = []; // 定義一個數組 保存全部的玩家
function Hero(name,teamColor) {
    this.friends = [];    //保存隊友列表
    this.enemies = [];    // 保存敵人列表
    this.state = 'live';  // 玩家狀態
    this.name = name;     // 角色名字
    this.teamColor = teamColor; // 隊伍的顏色
}
Hero.prototype.win = function(){
    // 贏了
    console.log("win:" + this.name);
};
Hero.prototype.lose = function(){
    // 輸了
    console.log("lose:" + this.name);
};
Hero.prototype.die = function(){
    // 全部隊友死亡狀況 默認都是活着的
    var all_dead = true;
    this.state = 'dead'; // 設置玩家狀態爲死亡
    for(var i = 0,ilen = this.friends.length; i < ilen; i+=1) {
        // 遍歷,若是還有一個隊友沒有死亡的話,則遊戲還未結束
        if(this.friends[i].state !== 'dead') {
            all_dead = false; 
            break;
        }
    }
    if(all_dead) {
        this.lose();  // 隊友所有死亡,遊戲結束
        // 循環 通知全部的玩家 遊戲失敗
        for(var j = 0,jlen = this.friends.length; j < jlen; j+=1) {
            this.friends[j].lose();
        }
        // 通知全部敵人遊戲勝利
        for(var j = 0,jlen = this.enemies.length; j < jlen; j+=1) {
            this.enemies[j].win();
        }
    }
}
// 定義一個工廠類來建立玩家 
var heroFactory = function(name,teamColor) {
    var newPlayer = new Hero(name,teamColor);
    for(var i = 0,ilen = players.length; i < ilen; i+=1) {
        // 若是是同一隊的玩家
        if(players[i].teamColor === newPlayer.teamColor) {
            // 相互添加隊友列表
            players[i].friends.push(newPlayer);
            newPlayer.friends.push(players[i]);
        }else {
            // 相互添加到敵人列表
            players[i].enemies.push(newPlayer);
            newPlayer.enemies.push(players[i]);
        }
    }
    players.push(newPlayer);
    return newPlayer;
};
        // 紅隊
var p1 = heroFactory("aa",'red'),
    p2 = heroFactory("bb",'red'),
    p3 = heroFactory("cc",'red'),
    p4 = heroFactory("dd",'red');
        
// 藍隊
var p5 = heroFactory("ee",'blue'),
    p6 = heroFactory("ff",'blue'),
    p7 = heroFactory("gg",'blue'),
    p8 = heroFactory("hh",'blue');
// 讓紅隊玩家所有死亡
p1.die();
p2.die();
p3.die();
p4.die();
// lose:dd lose:aa lose:bb lose:cc
// win:ee win:ff win:gg win:hh
複製代碼

如上代碼:Hero函數有2個參數,分別是name(玩家名字)和teamColor(隊顏色),

首先咱們能夠根據隊顏色來判斷是隊友仍是敵人;一樣也有三個方法win(贏),lose(輸),和die(死亡);若是每次死亡一我的的時候,循環下該死亡的隊友有沒有所有死亡,若是所有死亡了的話,就輸了,所以須要循環他們的隊友,分別告訴每一個隊友中的成員他們輸了,同時須要循環他們的敵人,分別告訴他們的敵人他們贏了;所以每次死了一我的的時候,都須要循環一次判斷他的隊友是否都死亡了;所以每一個玩家和其餘的玩家都是牢牢耦合在一塊兒了。

下面咱們可使用中介者模式來改善上面的demo;

首先咱們仍然定義Hero構造函數和Hero對象原型的方法,在Hero對象的這些原型方法中,再也不負責具體的執行的邏輯,而是把操做轉交給中介者對象,中介者對象來負責作具體的事情,咱們能夠把中介者對象命名爲playerDirector;

在playerDirector開放一個對外暴露的接口ReceiveMessage,負責接收player對象發送的消息,而player對象發送消息的時候,老是把自身的this做爲參數發送給playerDirector,以便playerDirector 識別消息來自於那個玩家對象。

代碼以下:

複製代碼
var players = []; // 定義一個數組 保存全部的玩家
function Hero(name,teamColor) {
    this.state = 'live';  // 玩家狀態
    this.name = name;     // 角色名字
    this.teamColor = teamColor; // 隊伍的顏色
}
Hero.prototype.win = function(){
    // 贏了
    console.log("win:" + this.name);
};
Hero.prototype.lose = function(){
    // 輸了
    console.log("lose:" + this.name);
};
// 死亡
Hero.prototype.die = function(){
    this.state = 'dead';
    // 給中介者發送消息,玩家死亡
    playerDirector.ReceiveMessage('playerDead',this);
}
// 移除玩家
Hero.prototype.remove = function(){
    // 給中介者發送一個消息,移除一個玩家
    playerDirector.ReceiveMessage('removePlayer',this);
};
// 玩家換隊
Hero.prototype.changeTeam = function(color) {
    // 給中介者發送一個消息,玩家換隊
    playerDirector.ReceiveMessage('changeTeam',this,color);
};
// 定義一個工廠類來建立玩家 
var heroFactory = function(name,teamColor) {
    // 建立一個新的玩家對象
    var newHero = new Hero(name,teamColor);
    // 給中介者發送消息,新增玩家
    playerDirector.ReceiveMessage('addPlayer',newHero);
    return newHero;
};
var playerDirector = (function(){
    var players = {},  // 保存全部的玩家
        operations = {}; // 中介者能夠執行的操做
    // 新增一個玩家操做
    operations.addPlayer = function(player) {
        // 獲取玩家隊友的顏色
        var teamColor = player.teamColor;
        // 若是該顏色的玩家尚未隊伍的話,則新成立一個隊伍
        players[teamColor] = players[teamColor] || [];
        // 添加玩家進隊伍
        players[teamColor].push(player);
     };
    // 移除一個玩家
    operations.removePlayer = function(player){
        // 獲取隊伍的顏色
        var teamColor = player.teamColor,
        // 獲取該隊伍的全部成員
        teamPlayers = players[teamColor] || [];
        // 遍歷
        for(var i = teamPlayers.length - 1; i>=0; i--) {
            if(teamPlayers[i] === player) {
                teamPlayers.splice(i,1);
            }
        }
    };
    // 玩家換隊
    operations.changeTeam = function(player,newTeamColor){
        // 首先從原隊伍中刪除
        operations.removePlayer(player);
        // 而後改變隊伍的顏色
        player.teamColor = newTeamColor;
        // 增長到隊伍中
        operations.addPlayer(player);
    };
    // 玩家死亡
operations.playerDead = function(player) {
    var teamColor = player.teamColor,
    // 玩家所在的隊伍
    teamPlayers = players[teamColor];

    var all_dead = true;
    //遍歷 
    for(var i = 0,player; player = teamPlayers[i++]; ) {
        if(player.state !== 'dead') {
            all_dead = false;
            break;
        }
    }
    // 若是all_dead 爲true的話 說明所有死亡
    if(all_dead) {
        for(var i = 0, player; player = teamPlayers[i++]; ) {
            // 本隊全部玩家lose
            player.lose();
        }
        for(var color in players) {
            if(color !== teamColor) {
                // 說明這是另一組隊伍
                // 獲取該隊伍的玩家
                var teamPlayers = players[color];
                for(var i = 0,player; player = teamPlayers[i++]; ) {
                    player.win(); // 遍歷通知其餘玩家win了
                }
            }
        }
    }
};
var ReceiveMessage = function(){
    // arguments的第一個參數爲消息名稱 獲取第一個參數
    var message = Array.prototype.shift.call(arguments);
    operations[message].apply(this,arguments);
};
return {
    ReceiveMessage : ReceiveMessage
};
})();
// 紅隊
var p1 = heroFactory("aa",'red'),
    p2 = heroFactory("bb",'red'),
    p3 = heroFactory("cc",'red'),
        p4 = heroFactory("dd",'red');
        
    // 藍隊
    var p5 = heroFactory("ee",'blue'),
        p6 = heroFactory("ff",'blue'),
        p7 = heroFactory("gg",'blue'),
        p8 = heroFactory("hh",'blue');
    // 讓紅隊玩家所有死亡
    p1.die();
    p2.die();
    p3.die();
    p4.die();
    // lose:aa lose:bb lose:cc lose:dd 
   // win:ee win:ff win:gg win:hh
複製代碼

咱們能夠看到如上代碼;玩家與玩家之間的耦合代碼已經解除了,而把全部的邏輯操做放在中介者對象裏面進去處理,某個玩家的任何操做不須要去遍歷去通知其餘玩家,而只是須要給中介者發送一個消息便可,中介者接受到該消息後進行處理,處理完消息以後會把處理結果反饋給其餘的玩家對象。使用中介者模式解除了對象與對象之間的耦合代碼; 使程序更加的靈活.

中介者模式實現購買商品的列子

下面的列子是書上的列子,好比在淘寶或者天貓的列子不是這樣實現的,也沒有關係,咱們能夠改動下便可,咱們最主要來學習下使用中介者模式來實現的思路。

首先先介紹一下業務:在購買流程中,能夠選擇手機的顏色以及輸入購買的數量,同時頁面中有2個展現區域,分別顯示用戶剛剛選擇好的顏色和數量。還有一個按鈕動態顯示下一步的操做,咱們須要查詢該顏色手機對應的庫存,若是庫存數量小於此次的購買數量,按鈕則被禁用而且顯示庫存不足的文案,反之按鈕高亮且能夠點擊而且顯示假如購物車。

HTML代碼以下:

複製代碼
選擇顏色:
    <select id="colorSelect">
        <option value="">請選擇</option>
        <option value="red">紅色</option>
        <option value="blue">藍色</option>
    </select>
    <p>輸入購買的數量: <input type="text" id="numberInput"/></p>
    你選擇了的顏色:<div id="colorInfo"></div>
    <p>你輸入的數量: <div id="numberInfo"></div> </p>
    <button id="nextBtn" disabled="true">請選擇手機顏色和購買數量</button>
複製代碼

首先頁面上有一個select選擇框,而後有輸入的購買數量輸入框,還有2個展現區域,分別是選擇的顏色和輸入的數量的顯示的區域,還有下一步的按鈕操做;

咱們先定義一下:

假設咱們提早從後臺獲取到全部顏色手機的庫存量

var goods = {
    // 手機庫存
    "red": 6,
    "blue": 8
};

接着 咱們下面分別來監聽colorSelect的下拉框的onchange事件和numberInput輸入框的oninput的事件,而後在這兩個事件中做出相應的處理

常規的JS代碼以下:

複製代碼
// 假設咱們提早從後臺獲取到全部顏色手機的庫存量
var goods = {
    // 手機庫存
    "red": 6,
    "blue": 8
};
/*
咱們下面分別來監聽colorSelect的下拉框的onchange事件和numberInput輸入框的oninput的事件,
而後在這兩個事件中做出相應的處理
*/
var colorSelect = document.getElementById("colorSelect"),
    numberInput = document.getElementById("numberInput"),
    colorInfo = document.getElementById("colorInfo"),
    numberInfo = document.getElementById("numberInfo"),
    nextBtn = document.getElementById("nextBtn");
        
// 監聽change事件
colorSelect.onchange = function(e){
    select();
};
numberInput.oninput = function(){
    select();
};
function select(){
    var color = colorSelect.value,   // 顏色
        number = numberInput.value,  // 數量
        stock = goods[color];  // 該顏色手機對應的當前庫存
            
    colorInfo.innerHTML = color;
    numberInfo.innerHTML = number;

    // 若是用戶沒有選擇顏色的話,禁用按鈕
    if(!color) {
        nextBtn.disabled = true;
        nextBtn.innerHTML = "請選擇手機顏色";
            return;
    }
    // 判斷用戶輸入的購買數量是不是正整數
    var reg = /^\d+$/g;
    if(!reg.test(number)) {
        nextBtn.disabled = true;
        nextBtn.innerHTML = "請輸入正確的購買數量";
        return;
    }
    // 若是當前選擇的數量大於當前的庫存的數量的話,顯示庫存不足
    if(number > stock) {
        nextBtn.disabled = true;
        nextBtn.innerHTML = "庫存不足";
        return;
    }
    nextBtn.disabled = false;
    nextBtn.innerHTML = "放入購物車";
}
複製代碼

上面的代碼雖然是完成了頁面上的需求,可是咱們的代碼都耦合在一塊兒了,目前雖然問題不是不少,假如隨着之後需求的改變,SKU屬性愈來愈多的話,好比頁面增長一個或者多個下拉框的時候,表明選擇手機內存,如今咱們須要計算顏色,內存和購買數量,來判斷nextBtn是顯示庫存不足仍是放入購物車;代碼以下:

HTML代碼以下:

複製代碼
選擇顏色:
    <select id="colorSelect">
        <option value="">請選擇</option>
        <option value="red">紅色</option>
        <option value="blue">藍色</option>
    </select>
    <br/>
    <br/>
    選擇內存:
    <select id="memorySelect">
        <option value="">請選擇</option>
        <option value="32G">32G</option>
        <option value="64G">64G</option>
    </select>
    <p>輸入購買的數量: <input type="text" id="numberInput"/></p>
    你選擇了的顏色:<div id="colorInfo"></div>
    你選擇了內存:<div id="memoryInfo"></div>
    <p>你輸入的數量: <div id="numberInfo"></div> </p>
    <button id="nextBtn" disabled="true">請選擇手機顏色和購買數量</button>
複製代碼

JS代碼變爲以下:

複製代碼
// 假設咱們提早從後臺獲取到全部顏色手機的庫存量
var goods = {
    // 手機庫存
    "red|32G": 6,
    "red|64G": 16,
    "blue|32G": 8,
    "blue|64G": 18
};
/*
咱們下面分別來監聽colorSelect的下拉框的onchange事件和numberInput輸入框的oninput的事件,
而後在這兩個事件中做出相應的處理
 */
var colorSelect = document.getElementById("colorSelect"),
    memorySelect = document.getElementById("memorySelect"),
    numberInput = document.getElementById("numberInput"),
    colorInfo = document.getElementById("colorInfo"),
    numberInfo = document.getElementById("numberInfo"),
    memoryInfo = document.getElementById("memoryInfo"),
    nextBtn = document.getElementById("nextBtn");
        
// 監聽change事件
colorSelect.onchange = function(){
    select();
};
numberInput.oninput = function(){
    select();
};
memorySelect.onchange = function(){
    select();    
};
function select(){
    var color = colorSelect.value,   // 顏色
        number = numberInput.value,  // 數量
        memory = memorySelect.value, // 內存
        stock = goods[color + '|' +memory];  // 該顏色手機對應的當前庫存
            
    colorInfo.innerHTML = color;
    numberInfo.innerHTML = number;
    memoryInfo.innerHTML = memory;
    // 若是用戶沒有選擇顏色的話,禁用按鈕
    if(!color) {
        nextBtn.disabled = true;
        nextBtn.innerHTML = "請選擇手機顏色";
            return;
        }
        // 判斷用戶輸入的購買數量是不是正整數
        var reg = /^\d+$/g;
        if(!reg.test(number)) {
            nextBtn.disabled = true;
            nextBtn.innerHTML = "請輸入正確的購買數量";
            return;
        }
        // 若是當前選擇的數量大於當前的庫存的數量的話,顯示庫存不足
        if(number > stock) {
            nextBtn.disabled = true;
            nextBtn.innerHTML = "庫存不足";
            return;
        }
        nextBtn.disabled = false;
        nextBtn.innerHTML = "放入購物車";
    }
複製代碼

通常的代碼就是這樣的,感受使用中介者模式代碼也相似,這裏就很少介紹了,書上的代碼說有優勢,可是我的感受沒有什麼很大的區別,所以這裏就再也不使用中介者模式來編寫代碼了。

==

相關文章
相關標籤/搜索