成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

《JavaScript 模式》知識(shí)點(diǎn)小抄本(上)

didikee / 3067人閱讀

摘要:?jiǎn)误w模式有以下優(yōu)點(diǎn)用來(lái)劃分命名空間,減少全局變量數(shù)量。通常我們使用操作符創(chuàng)建單體模式的三種選擇,讓構(gòu)造函數(shù)總返回最初的對(duì)象使用全局對(duì)象來(lái)存儲(chǔ)該實(shí)例不推薦,容易全局污染。實(shí)現(xiàn)該工廠模式并不困難,主要是要找到能夠穿件所需類型對(duì)象的構(gòu)造函數(shù)。

介紹

最近開始給自己每周訂個(gè)學(xué)習(xí)任務(wù),學(xué)習(xí)結(jié)果反饋為一篇文章的輸出,做好學(xué)習(xí)記錄。
這一周(02.25-03.03)我定的目標(biāo)是《JavaScript 模式》的第七章學(xué)習(xí)一遍,學(xué)習(xí)結(jié)果的反饋就是本篇文章啦。
由于內(nèi)容實(shí)在太長(zhǎng),我將本文分為兩部分:

《JavaScript 模式》知識(shí)點(diǎn)整理(上)

《JavaScript 模式》知識(shí)點(diǎn)整理(下)

本文內(nèi)容中主要參考《JavaScript 模式》,其中也有些案例是來(lái)自網(wǎng)上資料,有備注出處啦,如造成不便,請(qǐng)聯(lián)系我刪改。

過(guò)兩天我會(huì)把這篇文章收錄到我整理的知識(shí)庫(kù) 【Cute-JavaScript】 中,并已經(jīng)同步到 【github】上面。

一、單體模式(Singleton Pattern) 1.概念介紹

單體模式(Singleton Pattern)的思想在于保證一個(gè)特定類僅有一個(gè)實(shí)例,即不管使用這個(gè)類創(chuàng)建多少個(gè)新對(duì)象,都會(huì)得到與第一次創(chuàng)建的對(duì)象完全相同。

它讓我們能將代碼組織成一個(gè)邏輯單元,并可以通過(guò)單一變量進(jìn)行訪問(wèn)。

單體模式有以下優(yōu)點(diǎn):

用來(lái)劃分命名空間,減少全局變量數(shù)量。

使代碼組織的更一致,提高代碼閱讀性和維護(hù)性。

只能被實(shí)例化一次。

但在JavaScript中沒有類,只有對(duì)象。當(dāng)我們創(chuàng)建一個(gè)新對(duì)象,它都是個(gè)新的單體,因?yàn)镴avaScript中永遠(yuǎn)不會(huì)有完全相等的對(duì)象,除非它們是同一個(gè)對(duì)象。
因此,我們每次使用對(duì)象字面量創(chuàng)建對(duì)象的時(shí)候,實(shí)際上就是在創(chuàng)建一個(gè)單例

let a1 = { name : "leo" };
let a2 = { name : "leo" };
a1 === a2;  // false
a1 == a2;   // false

這里需要注意,單體模式有個(gè)條件,是該對(duì)象能被實(shí)例化,比如下面這樣就不是單體模式,因?yàn)樗荒鼙粚?shí)例化:

let a1 = {
    b1: 1, b2: 2,
    m1: function(){
        return this.b1;
    },
    m2: function(){
        return this.b2;
    }
}
new a1();  // Uncaught TypeError: a1 is not a constructor

下面展示一個(gè)單體模式的基本結(jié)構(gòu):

let Singleton = function (name){
    this.name = name;
    this.obj = null;
}
Singleton.prototype.getName = function(){
    return this.name;
}
function getObj(name){
    return this.obj || (this.obj = new Singleton(name));
}
let g1 = getObj("leo");
let g2 = getObj("pingan");
g1 === g2;    // true
g1 == g2;     // true
g1.getName(); // "leo"
g2.getName(); // "leo"

從這里可以看出,單體模式只能實(shí)例化一次,后面再調(diào)用的話,都是使用第一次實(shí)例化的結(jié)果。

2.應(yīng)用場(chǎng)景

單例模式只允許實(shí)例化一次,能提高對(duì)象訪問(wèn)速度并且節(jié)約內(nèi)存,通常被用于下面場(chǎng)景:

需要頻繁創(chuàng)建再銷毀的對(duì)象,或頻繁使用的對(duì)象:如:彈窗,文件;

常用的工具類對(duì)象;

常用的資源消耗大的對(duì)象;

3.實(shí)現(xiàn)彈框案例

這里我們要用單體模式,創(chuàng)建一個(gè)彈框,大概需要實(shí)現(xiàn):元素值創(chuàng)建一次,使用的時(shí)候直接調(diào)用。
因此我們這么做:

let create = (() => {
    let div;
    return () => {
        if(!div){
            div = document.createElement("div");
            div.innderHTML = "我是leo創(chuàng)建的彈框";
            div.style.display = "none";
            div.setAttribute("id", "leo");
            document.body.appendChild(div);
        }
        return div;
    }
})();
// 觸發(fā)事件
document.getElementById("otherBtn").onclick = () => {
    let first = create();
    first.style.display = "block";
}
4.使用new操作符

由于JavaScript中沒有類,但JavaScript有new語(yǔ)法來(lái)用構(gòu)造函數(shù)創(chuàng)建對(duì)象,并可以使用這種方法實(shí)現(xiàn)單體模式。
當(dāng)使用同一個(gè)構(gòu)造函數(shù)以new操作符創(chuàng)建多個(gè)對(duì)象,獲得的是指向完全相同的對(duì)象的新指針。

通常我們使用new操作符創(chuàng)建單體模式的三種選擇,讓構(gòu)造函數(shù)總返回最初的對(duì)象:

使用全局對(duì)象來(lái)存儲(chǔ)該實(shí)例(不推薦,容易全局污染)。

使用靜態(tài)屬性存儲(chǔ)該實(shí)例,無(wú)法保證該靜態(tài)屬性的私有性。

function Leo(name){
    if(typeof Leo.obj === "object"){
        return Leo.obj;
    }
    this.name = name;
    Leo.obj = this;
    return this;
}
let a1 = new Leo("leo");
let a2 = new Leo("pingan");
a1 === a2 ; // true
a1 ==  a2 ; // true

唯一的缺點(diǎn)就是obj屬性是公開的,容易被修改。

使用閉包將該實(shí)例包裹,保證實(shí)例是私有性并不會(huì)被外界修改。

我們這通過(guò)重寫上面的方法,加入閉包:

function Leo(name){
    let obj;
    this.name = name;
    obj = this;       // 1.存儲(chǔ)第一次創(chuàng)建的對(duì)象
    Leo = function(){ // 2.修改原來(lái)的構(gòu)造函數(shù)
        return obj;
    }
}
let a1 = new Leo("leo");
let a2 = new Leo("pingan");
a1 === a2 ; // true
a1 ==  a2 ; // true

當(dāng)我們第一次調(diào)用構(gòu)造函數(shù),像往常一樣返回this,而后面再調(diào)用的話,都將重寫構(gòu)造函數(shù),并訪問(wèn)私有變量obj并返回。

二、工廠模式(Factory Pattern) 1.概念介紹

工廠模式的目的在于創(chuàng)建對(duì)象,實(shí)現(xiàn)下列目標(biāo):

可重復(fù)執(zhí)行,來(lái)創(chuàng)建相似對(duì)象;

當(dāng)編譯時(shí)位置具體類型(類)時(shí),為調(diào)用者提供一種創(chuàng)建對(duì)象的接口;

通過(guò)工廠方法(或類)創(chuàng)建的對(duì)象,都繼承父對(duì)象,下面一個(gè)簡(jiǎn)單工廠方法理解:

function Person(name, age, sex){
    let p = {}; // 或 let p = new Object(); 創(chuàng)建一個(gè)初始對(duì)象
    p.name = name;
    p.age = age;
    p.sex = sex;
    p.ask = function(){
        return "my name is" + this.name;
    }
    return p;
}
let leo = new Person("leo", 18, "boy");
let pingan = new Person("pingan", 18, "boy");
console.log(leo.name, leo.age, leo.sex);          // "leo", 18, "boy"
console.log(pingan.name, pingan.age, pingan.sex); // "pingan", 18, "boy"

通過(guò)調(diào)用Person構(gòu)造函數(shù),我們可以像工廠那樣,生產(chǎn)出無(wú)數(shù)個(gè)包含三個(gè)屬性和一個(gè)方法的對(duì)象。
可以看出,工廠模式可以解決創(chuàng)建多個(gè)類似對(duì)象的問(wèn)題。

2.優(yōu)缺點(diǎn) 2.1優(yōu)點(diǎn)

一個(gè)調(diào)用者想創(chuàng)建一個(gè)對(duì)象,只要知道其名稱就可以了。

擴(kuò)展性高,如果想增加一個(gè)產(chǎn)品,只要擴(kuò)展一個(gè)工廠類就可以。

屏蔽產(chǎn)品的具體實(shí)現(xiàn),調(diào)用者只關(guān)心產(chǎn)品的接口。

2.2缺點(diǎn)

每次增加一個(gè)產(chǎn)品時(shí),都需要增加一個(gè)具體類和對(duì)象實(shí)現(xiàn)工廠,使得系統(tǒng)中類的個(gè)數(shù)成倍增加,在一定程度上增加了系統(tǒng)的復(fù)雜度,同時(shí)也增加了系統(tǒng)具體類的依賴。這并不是什么好事。

3.實(shí)現(xiàn)復(fù)雜工廠模式

在復(fù)雜工廠模式中,我們將其成員對(duì)象的實(shí)列化推遲到子類中,子類可以重寫父類接口方法以便創(chuàng)建的時(shí)候指定自己的對(duì)象類型。
父類類似一個(gè)公共函數(shù),只處理創(chuàng)建過(guò)程中的問(wèn)題,并且這些處理將被子類繼承,然后在子類實(shí)現(xiàn)專門功能。

比如這里我們需要實(shí)現(xiàn)這么一個(gè)實(shí)例:

需要一個(gè)公共父函數(shù)CarMaker;

父函數(shù)CarMaker有個(gè)factor靜態(tài)方法,用于創(chuàng)建car對(duì)象;

定義三個(gè)靜態(tài)屬性,值為三個(gè)函數(shù),用于繼承父函數(shù)CarMaker

然后我們希望這么使用這個(gè)函數(shù):

let c1 = CarMaker.factory("Car1");
let c2 = CarMaker.factory("Car2");
let c3 = CarMaker.factory("Car3");
c1.drirve();  // "我的編號(hào)是6"
c2.drirve();  // "我的編號(hào)是3"
c3.drirve();  // "我的編號(hào)是12"

可以看出,調(diào)用時(shí)接收以字符串形式指定類型,并返回請(qǐng)求類型的對(duì)象,并且這樣使用是不需要用new操作符。

下面看代碼實(shí)現(xiàn):

// 創(chuàng)建父構(gòu)造函數(shù)
function CarMaker(){};
CarMaker.prototype.drive = function(){
    return `我的編號(hào)是${this.id}`;
}
// 添加靜態(tài)工廠方法
CarMaker.factory = function (type){
    let types = type, newcar;
    // 若構(gòu)造函數(shù)不存在 則發(fā)生錯(cuò)誤
    if(typeof CarMaker[types] !== "function"){
        throw{ name: "Error", message: `${types}不存在`};
    }
    // 若構(gòu)造函數(shù)存在,則讓原型繼承父類,但僅繼承一次
    if(CarMaker[types].prototype.drive !== "function"){
        CarMaker[types].prototype = new CarMaker();
    }
    // 創(chuàng)建新實(shí)例,并返回
    newcar = new CarMaker[types]();
    return newcar;
}
// 調(diào)用
CarMaker.c1 = function(){
    this.id = 6;
}
CarMaker.c2 = function(){
    this.id = 3;
}
CarMaker.c3 = function(){
    this.id = 12;
}

定義完成后,我們?cè)賵?zhí)行前面的代碼:

let c1 = CarMaker.factory("Car1");
let c2 = CarMaker.factory("Car2");
let c3 = CarMaker.factory("Car3");
c1.drirve();  // "我的編號(hào)是6"
c2.drirve();  // "我的編號(hào)是3"
c3.drirve();  // "我的編號(hào)是12"

就能正常打印結(jié)果了。

實(shí)現(xiàn)該工廠模式并不困難,主要是要找到能夠穿件所需類型對(duì)象的構(gòu)造函數(shù)。
這里使用簡(jiǎn)單的映射來(lái)創(chuàng)建該對(duì)象的構(gòu)造函數(shù)。

4.內(nèi)置對(duì)象工廠

內(nèi)置的對(duì)象工廠,就像全局的Object()構(gòu)造函數(shù),也是工廠模式的行為,根據(jù)輸入類型創(chuàng)建不同對(duì)象。
如傳入一個(gè)原始數(shù)字,返回一個(gè)Number()構(gòu)造函數(shù)創(chuàng)建一個(gè)對(duì)象,傳入一個(gè)字符串或布爾值也成立。
對(duì)于傳入任何其他值,包括無(wú)輸入的值,都會(huì)創(chuàng)建一個(gè)常規(guī)的對(duì)象。

無(wú)論是否使用new操作符,都可以調(diào)用Object(),我們這么測(cè)試:

let a = new Object(), b = new Object(1),
    c = Object("1"),  d = Object(true);

a.constructor === Object;  // true 
b.constructor === Number;  // true 
c.constructor === String;  // true 
d.constructor === Boolean; // true 

事實(shí)上,Object()用途不大,這里列出來(lái)是因?yàn)樗俏覀儽容^常見的工廠模式。

三、迭代器模式(Iterator Pattern) 1.概念介紹

迭代器模式(Iterator Pattern)是提供一種方法,順序訪問(wèn)一個(gè)聚合對(duì)象中每個(gè)元素,并且不暴露該對(duì)象內(nèi)部。

這種模式屬于行為型模式,有以下幾個(gè)特點(diǎn):

訪問(wèn)一個(gè)聚合對(duì)象的內(nèi)容,而無(wú)需暴露它的內(nèi)部表示。

提供統(tǒng)一接口來(lái)遍歷不同結(jié)構(gòu)的數(shù)據(jù)集合。

遍歷的同事更改迭代器所在的集合結(jié)構(gòu)可能會(huì)導(dǎo)致問(wèn)題。

在迭代器模式中,通常包含有一個(gè)包含某種數(shù)據(jù)集合的對(duì)象,需要提供一種簡(jiǎn)單的方法來(lái)訪問(wèn)每個(gè)元素。
這里對(duì)象需要提供一個(gè)next()方法,每次調(diào)用都必須返回下一個(gè)連續(xù)的元素。

這里假設(shè)創(chuàng)建一個(gè)對(duì)象leo,我們通過(guò)調(diào)用它的next()方法訪問(wèn)下一個(gè)連續(xù)的元素:

let obj;
while(obj = leo.next()){
    // do something
    console.log(obj);
}

另外迭代器模式中,聚合對(duì)象還會(huì)提供一個(gè)更為漸變的hasNext()方法,來(lái)檢查是否已經(jīng)到達(dá)數(shù)據(jù)末尾,我們這么修改前面的代碼:

while(leo.hasNext()){
    // do something
    console.log(obj);
}
2.優(yōu)缺點(diǎn)和應(yīng)用場(chǎng)景 2.1優(yōu)點(diǎn)

它簡(jiǎn)化了聚合類,并支持以不同的方式遍歷一個(gè)聚合對(duì)象。

在同一個(gè)聚合上可以有多個(gè)遍歷。

在迭代器模式中,增加新的聚合類和迭代器類都很方便,無(wú)須修改原有代碼。

2.2缺點(diǎn)

由于迭代器模式將存儲(chǔ)數(shù)據(jù)和遍歷數(shù)據(jù)的職責(zé)分離,增加新的聚合類需要對(duì)應(yīng)增加新的迭代器類,類的個(gè)數(shù)成對(duì)增加,這在一定程度上增加了系統(tǒng)的復(fù)雜性。

2.3應(yīng)用場(chǎng)景

訪問(wèn)一個(gè)聚合對(duì)象的內(nèi)容而無(wú)須暴露它的內(nèi)部表示。

需要為聚合對(duì)象提供多種遍歷方式。

為遍歷不同的聚合結(jié)構(gòu)提供一個(gè)統(tǒng)一的接口。

3.簡(jiǎn)單案例

根據(jù)上面的介紹,我們這里實(shí)現(xiàn)一個(gè)簡(jiǎn)單案例,將設(shè)我們數(shù)據(jù)只是普通數(shù)組,然后每次檢索,返回的是間隔一個(gè)的數(shù)組元素(即不是連續(xù)返回):

let leo = (function(){
    let index = 0, data = [1, 2, 3, 4, 5],
        len = data.length;
    return {
        next: function(){
            let obj;
            if(!this.hasNext()){
                return null;
            };
            obj = data[index];
            index = index + 2;
            return obj;
        },
        hasNext: function(){
            return index < len;
        }
    }
})()

然后我們還要給它提供更簡(jiǎn)單的訪問(wèn)方式和多次迭代數(shù)據(jù)的能力,我們需要添加下面兩個(gè)方法:

rewind() 重置指針到初始位置;

current() 返回當(dāng)前元素,因?yàn)楫?dāng)指針步前進(jìn)時(shí)無(wú)法使用next()操作;

代碼變成這樣:

let leo = (function(){
    //.. 
    return {
         // .. 
         rewind: function(){
             index = 0;
         },
         current: function(){
             return data[index];
         }
    }
})();

這樣這個(gè)案例就完整了,接下來(lái)我們來(lái)測(cè)試:

// 讀取記錄
while(leo.hasNext()){
    console.log(leo.next());
};  // 打印 1 3 5
// 回退
leo.rewind();
// 獲取當(dāng)前
console.log(leo.current()); // 回到初始位置,打印1
4.應(yīng)用場(chǎng)景

迭代器模式通常用于:對(duì)于集合內(nèi)部結(jié)果常常變化各異,我們不想暴露其內(nèi)部結(jié)構(gòu)的話,但又響讓客戶代碼透明底訪問(wèn)其中的元素,這種情況下我們可以使用迭代器模式。

簡(jiǎn)單理解:遍歷一個(gè)聚合對(duì)象。

jQuery應(yīng)用例子:

jQuery中的$.each()方法,可以讓我們傳入一個(gè)方法,實(shí)現(xiàn)對(duì)所有項(xiàng)的迭代操作:

$.each([1,2,3,4,5],function(index, value){
    console.log(`${index}: ${value}`)
})

使用迭代器模式實(shí)現(xiàn)each()方法

let myEach = function(arr, callback){
    for(var i = 0; i< arr.length; i++){
        callback(i, arr[i]);
    }
}
4.小結(jié)

迭代器模式是一種相對(duì)簡(jiǎn)單的模式,目前絕大多數(shù)語(yǔ)言都內(nèi)置了迭代器。而且迭代器模式也是非常常用,有時(shí)候不經(jīng)意就是用了。

四、裝飾者模式(Decorator Pattern) 1.概念介紹

裝飾者模式(Decorator Pattern):在不改變?cè)惡屠^承情況下,動(dòng)態(tài)添加功能到對(duì)象中,通過(guò)包裝一個(gè)對(duì)象實(shí)現(xiàn)一個(gè)新的具有原對(duì)象相同接口的新對(duì)象。

裝飾者模式有以下特點(diǎn):

添加功能時(shí)不改變?cè)瓕?duì)象結(jié)構(gòu)。

裝飾對(duì)象和原對(duì)象提供的接口相同,方便按照源對(duì)象的接口來(lái)使用裝飾對(duì)象。

裝飾對(duì)象中包含原對(duì)象的引用。即裝飾對(duì)象是真正的原對(duì)象包裝后的對(duì)象。

實(shí)際上,裝飾著模式的一個(gè)比較方便的特征在于其預(yù)期行為的可定制和可配置特性。從只有基本功能的普通對(duì)象開始,不斷增強(qiáng)對(duì)象的一些功能,并按照順序進(jìn)行裝飾。

2.優(yōu)缺點(diǎn)和應(yīng)用場(chǎng)景 2.1優(yōu)點(diǎn)

裝飾類和被裝飾類可以獨(dú)立發(fā)展,不會(huì)相互耦合,裝飾模式是繼承的一個(gè)替代模式,裝飾模式可以動(dòng)態(tài)擴(kuò)展一個(gè)實(shí)現(xiàn)類的功能。

2.2缺點(diǎn)

多層裝飾比較復(fù)雜。

2.3應(yīng)用場(chǎng)景

擴(kuò)展一個(gè)類的功能。

動(dòng)態(tài)增加功能,動(dòng)態(tài)撤銷。

3.基本案例

我們這里實(shí)現(xiàn)一個(gè)基本對(duì)象sale,可以通過(guò)sale對(duì)象獲取不同項(xiàng)目的價(jià)格,并通過(guò)調(diào)用sale.getPrice()方法返回對(duì)應(yīng)價(jià)格。并且在不同情況下,用額外的功能來(lái)裝飾它,會(huì)得到不同情況下的價(jià)格。

3.1創(chuàng)建對(duì)象

這里我們假設(shè)客戶需要支付國(guó)家稅和省級(jí)稅。按照裝飾者模式,我們就需要使用國(guó)家稅和省級(jí)稅兩個(gè)裝飾者來(lái)裝飾這個(gè)sale對(duì)象,然后在對(duì)使用價(jià)格格式化功能的裝飾者裝飾。實(shí)際看起來(lái)是這樣:

let sale = new Sale(100);
sale = sale.decorate("country");
sale = sale.decorate("privince");
sale = sale.decorate("money");
sale.getPrice();

使用裝飾者模式后,每個(gè)裝飾都非常靈活,主要根據(jù)其裝飾者順序,于是如果客戶不需要上繳國(guó)家稅,代碼就可以這么實(shí)現(xiàn):

let sale = new Sale(100);
sale = sale.decorate("privince");
sale = sale.decorate("money");
sale.getPrice();
3.2實(shí)現(xiàn)對(duì)象

接下來(lái)我們需要考慮的是如何實(shí)現(xiàn)Sale對(duì)象了。

實(shí)現(xiàn)裝飾者模式的其中一個(gè)方法是使得每個(gè)裝飾者成為一個(gè)對(duì)象,并且該對(duì)象包含了應(yīng)該被重載的方法。每個(gè)裝飾者實(shí)際上繼承了目前已經(jīng)被前一個(gè)裝飾者進(jìn)行裝飾后的對(duì)象,每個(gè)裝飾方法在uber(繼承的對(duì)象)上調(diào)用同樣的方法并獲取值,此外還繼續(xù)執(zhí)行一些操作。

uber關(guān)鍵字類似Java的super,它可以讓某個(gè)方法調(diào)用父類的方法,uber屬性指向父類原型。

即:當(dāng)我們調(diào)用sale.getPrice()方法時(shí),會(huì)調(diào)用money裝飾者的方法,然后每個(gè)裝飾方法都會(huì)先調(diào)用父對(duì)象的方法,因此一直往上調(diào)用,直到開始的Sale構(gòu)造函數(shù)實(shí)現(xiàn)的未被裝飾的getPrice()方法。理解如下圖:

我們這里可以先實(shí)現(xiàn)構(gòu)造函數(shù)Sale()和原型方法getPrice()

function Sale (price){
    this.price = price || 100;
}
Sale.prototype.getPrice = function (){
    return this.price;
}

并且裝飾者對(duì)象都將以構(gòu)造函數(shù)的屬性來(lái)實(shí)現(xiàn):

Sale.decorators = {};

接下來(lái)實(shí)現(xiàn)country這個(gè)裝飾者并實(shí)現(xiàn)它的getPrice(),改方法首先從父對(duì)象的方法獲取值再做修改:

Sale.decorators.country = {
    getPrice: function(){
        let price = this.uber.getPrice(); // 獲取父對(duì)象的值
        price += price * 5 / 100;
        return price;
    }
}

按照相同方法,實(shí)現(xiàn)其他裝飾者:

Sale.decorators.privince = {
    getPrice: function(){
        let price = this.uber.getPrice();
        price += price * 7 / 100;
        return price;
    }
}
Sale.decorators.money = {
    getPrice: function(){
        return "¥" + this.uber.getPrice().toFixed(2);
    }
}

最后我們還需要實(shí)現(xiàn)前面的decorate()方法,它將我們所有裝飾者拼接一起,并且做了下面的事情:
創(chuàng)建了個(gè)新對(duì)象newobj,繼承目前我們所擁有的對(duì)象(Sale),無(wú)論是原始對(duì)象還是最后裝飾后的對(duì)象,這里就是對(duì)象this,并設(shè)置newobjuber屬性,便于子對(duì)象訪問(wèn)父對(duì)象,然后將所有裝飾者的額外屬性復(fù)制到newobj中,返回newobj,即成為更新的sale對(duì)象:

Sale.prototype.decorate = function(decorator){
    let F = function(){}, newobj,
        overrides = this.constructor.decorators[decorator];
    F.prototype = this;
    newobj = new F();
    newobj.user = F.prototype;
    for(let k in overrides){
        if(overrides.hasOwnProperty(k)){
            newobj[k] = overrides[k];
        }
    }
    return newobj;
}
4.改造基本案例

這里我們使用列表實(shí)現(xiàn)相同功能,這個(gè)方法利用JavaScript語(yǔ)言的動(dòng)態(tài)性質(zhì),并且不需要使用繼承,也不需要讓每個(gè)裝飾方法調(diào)用鏈中前面的方法,可以簡(jiǎn)單的將前面方法的結(jié)果作為參數(shù)傳遞給下一個(gè)方法。

這樣實(shí)現(xiàn)也有個(gè)好處,支持反裝飾或撤銷裝飾,我們還是實(shí)現(xiàn)以下功能:

let sale = new Sale(100);
sale = sale.decorate("country");
sale = sale.decorate("privince");
sale = sale.decorate("money");
sale.getPrice();

現(xiàn)在的Sale()構(gòu)造函數(shù)中多了個(gè)裝飾者列表的屬性:

function Sale(price){
    this.price = (price > 0) || 100;
    this.decorators_list = [];
}

然后還是需要實(shí)現(xiàn)Sale.decorators,這里的getPrice()將變得更簡(jiǎn)單,也沒有去調(diào)用父對(duì)象的getPrice(),而是將結(jié)果作為參數(shù)傳遞:

Sale.decorators = {};
Sale.decorators.country = {
    getPrice: function(price){
        return price + price * 5 / 100;
    }
}
Sale.decorators.privince = {
    getPrice: function(price){
        return price + price * 7 / 100;
    }
}
Sale.decorators.money = {
    getPrice: function(price){
        return "¥" + this.uber.getPrice().toFixed(2);
    }
}

而這時(shí)候父對(duì)象的decorate()getPrice()變得復(fù)雜,decorate()用于追加裝飾者列表,getPrice()需要完成包括遍歷當(dāng)前添加的裝飾者一級(jí)調(diào)用每個(gè)裝飾者的getPrice()方法、傳遞從前一個(gè)方法獲得的結(jié)果:

Sale.prototype.decorate = function(decorators){
    this.decorators_list.push(decorators);
}

Sale.propotype.getPrice = function(){
    let price = this.price, name;
    for(let i = 0 ;i< this.decorators_list.length; i++){
        name = this.decorators_list[i];
        price = Sale.decorators[name].getPrice(price);
    }
    return price;
}
5.對(duì)比兩個(gè)方法

很顯然,第二種列表實(shí)現(xiàn)方法會(huì)更簡(jiǎn)單,不用設(shè)計(jì)繼承,并且裝飾方法也簡(jiǎn)單。
案例中getPrice()是唯一可以裝飾的方法,如果想實(shí)現(xiàn)更多可以被裝飾的方法,我們可以抽一個(gè)方法,來(lái)將每個(gè)額外的裝飾方法重復(fù)遍歷裝飾者列表中的這塊代碼,通過(guò)它來(lái)接收方法并使其成為“可裝飾”的方法。這樣實(shí)現(xiàn),saledecorators_list屬性會(huì)成為一個(gè)對(duì)象,且該對(duì)象每個(gè)屬性都是以裝飾者對(duì)象數(shù)組中的方法和值命名。

五、策略模式(Strategy Pattern) 1.概念介紹

策略模式(Strategy Pattern):封裝一系列算法,支持我們?cè)谶\(yùn)行時(shí),使用相同接口,選擇不同算法。它的目的是為了將算法的使用與算法的實(shí)現(xiàn)分離開來(lái)。

策略模式通常會(huì)有兩部分組成,一部分是策略類,它負(fù)責(zé)實(shí)現(xiàn)通用的算法,另一部分是環(huán)境類,它用戶接收客戶端請(qǐng)求并委托給策略類。

2.優(yōu)缺點(diǎn) 2.1優(yōu)點(diǎn)

有效地避免多重條件選擇語(yǔ)句;

支持開閉原則,將算法獨(dú)立封裝,使得更加便于切換、理解和擴(kuò)展;

更加便于代碼復(fù)用;

2.2缺點(diǎn)

策略類會(huì)增多;

所有策略類都需要對(duì)外暴露;

3.基本案例

我們可以很簡(jiǎn)單的將策略和算法直接做映射:

let add = {
    "add3" : (num) => num + 3,
    "add5" : (num) => num + 5,
    "add10": (num) => num + 10,
}
let demo = (type, num) => add[type](num);
console.log(demo("add3", 10));  // 13
console.log(demo("add10", 12)); // 22

然后我們?cè)侔衙總€(gè)策略的算法抽出來(lái):

let fun3  = (num) => num + 3;
let fun5  = (num) => num + 5;
let fun10 = (num) => num + 10;
let add = {
    "add3" : (num) => fun3(num),
    "add5" : (num) => fun5(num),
    "add10": (num) => fun10(num),
}
let demo = (type, num) => add[type](num);
console.log(demo("add3", 10));  // 13
console.log(demo("add10", 12)); // 22
4.表單驗(yàn)證案例

我們需要使用策略模式,實(shí)現(xiàn)一個(gè)處理表單驗(yàn)證的方法,無(wú)論表單的具體類型是什么都會(huì)調(diào)用驗(yàn)證方法。我們需要讓驗(yàn)證器能選擇最佳的策略來(lái)處理任務(wù),并將具體的驗(yàn)證數(shù)據(jù)委托給適當(dāng)算法。

我們假設(shè)需要驗(yàn)證下面的表單數(shù)據(jù)的有效性:

let data = {
    name    : "pingan",
    age     : "unknown",
    nickname: "leo",
}

這里需要先配置驗(yàn)證器,對(duì)表單數(shù)據(jù)中不同的數(shù)據(jù)使用不同的算法:

validator.config = {
    name    : "isNonEmpty",
    age     : "isNumber",
    nickname: "isAlphaNum",
}

并且我們需要將驗(yàn)證的錯(cuò)誤信息打印到控制臺(tái):

validator.validate(data);
if(validator.hasErrors()){
    console.log(validator.msg.join("
"));
}

接下來(lái)我們才要實(shí)現(xiàn)validator中具體的驗(yàn)證算法,他們都有一個(gè)相同接口validator.types,提供validate()方法和instructions幫助信息:

// 非空值檢查
validator.types.isNonEmpty = {
    validate: function(value){
        return value !== "";
    }
    instructions: "該值不能為空"
}

// 數(shù)值類型檢查
validator.types.isNumber = {
    validate: function(value){
        return !isNaN(value);
    }
    instructions: "該值只能是數(shù)字"
}

// 檢查是否只包含數(shù)字和字母
validator.types.isAlphaNum = {
    validate: function(value){
        return !/[^a-z0-9]/i.test(value);
    }
    instructions: "該值只能包含數(shù)字和字母,且不包含特殊字符"
}

最后就是要實(shí)現(xiàn)最核心的validator對(duì)象:

let validator = {
    types: {}, // 所有可用的檢查
    msg:[],    // 當(dāng)前驗(yàn)證的錯(cuò)誤信息
    config:{}, // 驗(yàn)證配置
    validate: function(data){ // 接口方法
        let type, checker, result;
        this.msg = []; // 清空錯(cuò)誤信息
        for(let k in data){
            if(data.hasOwnProperty(k)){
                type = this.config[k];
                checker = this.types[type];
                if(!type) continue;  // 不存在類型 則 不需要驗(yàn)證
                if(!checker){
                    throw {
                        name: "驗(yàn)證失敗",
                        msg: `不能驗(yàn)證類型:${type}`
                    }
                }
                result = checker.validate(data[k]);
                if(!result){
                    this.msg.push(`無(wú)效的值:${k},${checker.instructions}`);
                }
            }
        }
        return this.hasErrors();
    }
    hasErrors: function(){
        return this.msg.length != 0;
    }
}

總結(jié)這個(gè)案例,我們可以看出validator對(duì)象是通用的,需要增強(qiáng)validator對(duì)象的方法只需添加更多的類型檢查,后續(xù)針對(duì)每個(gè)新的用例,只需配置驗(yàn)證器和運(yùn)行validator()方法就可以。

5.小結(jié)

日常開發(fā)的時(shí)候,還是需要根據(jù)實(shí)際情況來(lái)選擇設(shè)計(jì)模式,而不能為了設(shè)計(jì)模式而去設(shè)計(jì)模式。通過(guò)上面的學(xué)習(xí),我們使用策略模式來(lái)避免多重條件判斷,并且通過(guò)開閉原則來(lái)封裝方法。我們應(yīng)該多在開發(fā)中,逐漸積累自己的開發(fā)工具庫(kù),便于以后使用。

參考資料

《JavaScript Patterns》

Author 王平安
E-mail [email protected]
博 客 www.pingan8787.com
微 信 pingan8787
每日文章推薦 https://github.com/pingan8787...
JS小冊(cè) js.pingan8787.com
微信公眾號(hào) 前端自習(xí)課

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/102339.html

相關(guān)文章

  • JavaScript 模式識(shí)點(diǎn)抄本(下)

    摘要:缺點(diǎn)不符合開閉原則,如果要改東西很麻煩,繼承重寫都不合適。預(yù)防低水平人員帶來(lái)的風(fēng)險(xiǎn)。開閉原則,高拓展性。這里的訂閱者稱為觀察者,而被觀察者稱為發(fā)布者,當(dāng)一個(gè)事件發(fā)生,發(fā)布者會(huì)發(fā)布通知所有訂閱者,并常常以事件對(duì)象形式傳遞消息。 介紹 最近開始給自己每周訂個(gè)學(xué)習(xí)任務(wù),學(xué)習(xí)結(jié)果反饋為一篇文章的輸出,做好學(xué)習(xí)記錄。 這一周(02.25-03.03)我定的目標(biāo)是《JavaScript 模式》...

    xiguadada 評(píng)論0 收藏0
  • JavaScript 正則表達(dá)式迷你書》識(shí)點(diǎn)抄本

    摘要:介紹這周開始學(xué)習(xí)老姚大佬的正則表達(dá)式迷你書,然后習(xí)慣性的看完一遍后,整理一下知識(shí)點(diǎn),便于以后自己重新復(fù)習(xí)。感謝原書作者老姚,本文無(wú)意抄襲,只是作為自己知識(shí)點(diǎn)的整理,后續(xù)也會(huì)整理到自己的知識(shí)庫(kù)網(wǎng)站中。等價(jià)于,表示出現(xiàn)次。 showImg(https://segmentfault.com/img/remote/1460000018530584?w=919&h=449); 介紹 這周開始學(xué)習(xí)...

    zollero 評(píng)論0 收藏0
  • 用純CSS實(shí)現(xiàn)優(yōu)雅的tab頁(yè)

    摘要:部分如上,四個(gè)區(qū)塊,四大名著,嘎嘎代碼如上,寫的很爛,輕噴用來(lái)控制元素的顯示和隱藏,實(shí)際上是為了實(shí)現(xiàn)動(dòng)畫效果此處有裝逼的嫌疑,因?yàn)闀?huì)阻礙,而不會(huì),另外也可以用來(lái)代替。 說(shuō)明 又是一個(gè)練手的小玩意兒,本身沒什么技術(shù)含量,就是幾個(gè)不常用的CSS3特性的結(jié)合而已。 要點(diǎn) Label標(biāo)簽的for屬性 單選框的:checked偽類 CSS的加號(hào)[+]選擇器 效果圖 showImg(https...

    lavnFan 評(píng)論0 收藏0
  • 用純CSS實(shí)現(xiàn)優(yōu)雅的tab頁(yè)

    摘要:部分如上,四個(gè)區(qū)塊,四大名著,嘎嘎代碼如上,寫的很爛,輕噴用來(lái)控制元素的顯示和隱藏,實(shí)際上是為了實(shí)現(xiàn)動(dòng)畫效果此處有裝逼的嫌疑,因?yàn)闀?huì)阻礙,而不會(huì),另外也可以用來(lái)代替。 說(shuō)明 又是一個(gè)練手的小玩意兒,本身沒什么技術(shù)含量,就是幾個(gè)不常用的CSS3特性的結(jié)合而已。 要點(diǎn) Label標(biāo)簽的for屬性 單選框的:checked偽類 CSS的加號(hào)[+]選擇器 效果圖 showImg(https...

    Ali_ 評(píng)論0 收藏0
  • Authy – 二次密碼保護(hù)驗(yàn)證必備軟件工具(親測(cè)Authy下載和使用)

    摘要:第二安裝設(shè)置這里老蔣有直接下載客戶端后,打開需要設(shè)置賬戶。總結(jié),老蔣個(gè)人覺得如果用二次密碼驗(yàn)證比用短信二次驗(yàn)證好很多,因?yàn)槎绦庞芯W(wǎng)絡(luò)的問(wèn)題海外商家無(wú)法推送短信驗(yàn)證碼,而是比較普遍的方法。隨著互聯(lián)網(wǎng)普遍于我們的生活中,我們?nèi)魏蔚馁~戶是不是都是數(shù)字化的。如果稍有不慎密碼泄露是不是可能導(dǎo)致賬戶安全遭受影響。比如我們有很多站長(zhǎng)朋友擁有價(jià)值連城的域名,重要服務(wù)器賬戶數(shù)據(jù)的,如果賬戶出現(xiàn)安全問(wèn)題,那損失...

    Astrian 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<