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

一:工廠模式javascript

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

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

​
function CreatePerson(name) {
    var obj = new Object();
    obj.name = name;
    obj.sayName = function(){
        return this.name;
    }
    return obj;
}
var p1 = new CreatePerson("longen");
var p2 = new CreatePerson("tugenhua");

console.log(p1.name); // longen
console.log(p1.sayName()); // longen

console.log(p2.name);  // tugenhua
console.log(p2.sayName()); // tugenhua

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

​

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

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

優勢:能解決多個類似的問題。數組

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

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

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

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

父類的構造函數以下:

// 定義自行車的構造函數
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,
    method1: function(){
        return this.attr1;
    },
};

如上面只是簡單的字面量結構,上面的全部成員變量都是經過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++) {
        (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

總結:有什麼不懂的問我!!!!

相關文章
相關標籤/搜索