国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

前端開發(fā)中常用的javascript設(shè)計(jì)模式

趙春朋 / 732人閱讀

摘要:代理模式,迭代器模式,單例模式,裝飾者模式最少知識(shí)原則一個(gè)軟件實(shí)體應(yīng)當(dāng)盡可能少地與其他實(shí)體發(fā)生相互作用。迭代器模式可以將迭代的過程從業(yè)務(wù)邏輯中分離出來,在使用迭代器模式之后,即不用關(guān)心對(duì)象內(nèi)部構(gòu)造也可以按順序訪問其中的每個(gè)元素。

接手項(xiàng)目越來越復(fù)雜的時(shí)候,有時(shí)寫完一段代碼,總感覺代碼還有優(yōu)化的空間,卻不知道從何處去下手。設(shè)計(jì)模式主要目的是提升代碼可擴(kuò)展性以及可閱讀性。

本文主要以例子的方式展示設(shè)計(jì)模式應(yīng)該如何使用!(例子主要來源于javascript設(shè)計(jì)模式一書,如果已經(jīng)對(duì)這本書讀得滾瓜爛熟的,可以劃過,如果還未讀,或者想了解一下可以收藏起來慢慢看~)

設(shè)計(jì)原則

在使用設(shè)計(jì)模式前應(yīng)該需要知道的幾個(gè)原則(其中對(duì)應(yīng)設(shè)計(jì)模式滿足對(duì)應(yīng)原則):

單一職責(zé)原則(SRP): 一個(gè)對(duì)象(只做一件事)。

代理模式,迭代器模式,單例模式,裝飾者模式

最少知識(shí)原則(LKP): 一個(gè)軟件實(shí)體應(yīng)當(dāng)盡可能少地與其他實(shí)體發(fā)生相互作用。

中介者模式

開放-封閉原則(OCP):軟件實(shí)體(類,模塊,函數(shù))應(yīng)該都是可以擴(kuò)展,但是不可修改

發(fā)布-訂閱模式,模板方法模式,策略模式,代理模式,職責(zé)鏈模式

代理模式
代理顧名思義,就是客服無法直接與本體進(jìn)行的溝通通過第三方進(jìn)行轉(zhuǎn)述。

虛擬代理
作為創(chuàng)建開銷大的對(duì)象的代表;虛擬代理經(jīng)常直到我們真正需要一個(gè)對(duì)象的時(shí)候才創(chuàng)建它;當(dāng)對(duì)象在創(chuàng)建前或創(chuàng)建中時(shí),由虛擬代理來扮演對(duì)象的替身;對(duì)象創(chuàng)建后,代理就會(huì)將請(qǐng)求直接委托給對(duì)象;
圖片預(yù)加載例子
const myImage = (function() {
    const imgNode = document.createElement("img");
    document.body.appendChild(imgNode);
    return {
        setSrc: function(src) {
            imgNode.src = src;
        }
    }
}())

// 代理容器
const proxyImage = (function() {
    let img = new Image();
    
    // 加載完之后將設(shè)置為添加的圖片
    img.onload = function() {
        myImage.setSrc(this.src)
    }
    return {
        setSrc: function(src) {
            myImage.setSrc("loading.gif");
            img.src = src;
        }
    }
}())

proxyImage.setSrc("file.jpg")

如上:代理容器控制了客戶對(duì)MyImage的訪問,并且在過程中加了一些額外的操作。

緩存代理
緩存代理可以為一些開銷大的運(yùn)算結(jié)果提供暫時(shí)的存儲(chǔ),在下次運(yùn)算時(shí),如果傳遞進(jìn)來的參數(shù)跟之前一致,則可以直接返回前面存儲(chǔ)的運(yùn)算結(jié)果。
計(jì)算乘積例子
// 求乘積函數(shù)(專注于自身職責(zé),計(jì)算成績(jī),緩存由代理實(shí)現(xiàn))
const mult = function() {
    let a = 1;
    for (let i = 0, l =arguments.length; i< l; i++){
        a = a * arguments[i];
    }
    return a;
}

// proxyMult 
const proxyMult = (function() {
    let cache = {};
    return function() {
        let args = Array.prototype.join.call(arguments, ",");
        if (args in cache) {
            return cache[args];
        }
        return cache[arg] = mult.apply(this, arguments);
    }
}())

proxyMult(1, 2, 3) // 6
proxyMult(1, 2, 3) // 6
迭代器模式

迭代器模式是指提供一種方法順序訪問一個(gè)聚合對(duì)象中的各個(gè)元素,而又不需要暴露該對(duì)象的內(nèi)部表達(dá)式。迭代器模式可以將迭代的過程從業(yè)務(wù)邏輯中分離出來,在使用迭代器模式之后,即不用關(guān)心對(duì)象內(nèi)部構(gòu)造也可以按順序訪問其中的每個(gè)元素。

迭代器分為內(nèi)部迭代器和外部迭代器,內(nèi)部迭代器,是在函數(shù)內(nèi)部已經(jīng)定義好了迭代規(guī)則,外部只需要調(diào)用即可。但如果要修改需求,那就要去迭代函數(shù)內(nèi)部去修改邏輯了。外部迭代器指的是必須顯示的請(qǐng)求迭代下一個(gè)元素。

如現(xiàn)在有一個(gè)需求,判斷兩個(gè)函數(shù)是否完全相等,分別使用內(nèi)部迭代器和外部迭代器去實(shí)現(xiàn)。

// 使用內(nèi)部迭代的方式實(shí)現(xiàn) compare
const compare = function (arr1, arr2) {
    try {
        if (arr1.length !== arr2.length) {
            throw "arr1和arr2不相等"
        }
        // forEach 相當(dāng)一于一個(gè)迭代器
        arr1.forEach((item, index) => {
            if (item !== arr2[index]) {
                throw "arr1和arr2不相等"
            }
        })
        console.log("arr1等于arr2")
    } catch (e) {
        console.log(e)
    }
}

使用外部迭代器模式改寫compare

// 迭代器
const iterator = function (obj) {
    let current = 0;
    let next = function () {
        current += 1;
    };
    let isDone = function () {
        return current >= obj.length;
    }
    let getCurrItem = function () {
        return obj[current];
    }
    return {
        next: next,
        isDone: isDone,
        getCurrItem: getCurrItem,
        length: obj.length
    }
}
// 重寫compare
const compare = function (iterator1, iterator2) {
    try {
        if (iterator1.length !== iterator2.length) {
            throw "iterator1不等于iterator2"
        }
        while (!iterator1.isDone() && !iterator2.isDone()) {
            if (iterator1.getCurrItem() !== iterator2.getCurrItem()) {

                throw "iterator1不等于iterator2"
            }
            iterator1.next();
            iterator2.next();
        }
        console.log("iterator1 === iterator2")
    } catch (e) {
        console.log(e)
    }
}
const iterator1 = iterator([1, 2, 3]);
const iterator2 = iterator([1, 2, 3]);
compare(iterator1, iterator2)

迭代器實(shí)際場(chǎng)景的應(yīng)用

根據(jù)不同的瀏覽器獲取相應(yīng)上傳組件對(duì)象
// 常規(guī)寫法
const getUploadObj = function() {
    try {
        return new ActiveXObject("txftna")
    } catch (e) {
        if (supportFlash()) {
            let str = ``
            return document.body.appendChild(str)
        } else {
            let str = ``;
            return document.body.appendChild(str);
        }
    }
}
// 迭代模式改寫

// IE上傳控件
const getActiveUploadObj = function () {
    try {
        return new ActiveXObject("TXFTNActiveX.FTNUPload")
    } catch (e) {
        return false;
    }
}

// flash上傳控件
const getFlashUploadObj = function () {
    if (supportFlash()) {
        let str = ``
        return document.body.appendChild(str)
    }
    return false
}
// 表單上傳
const getFormUploadObj = function () {
    let str = ``;
    return document.body.appendChild(str);
}
// 使用迭器執(zhí)行
const iteratorUploadObj = function () {
    for (let i = 0, fn; fn = arguments[i++];) {
        const uploadInstane = fn()
        if (uploadInstane !== false) {
            return uploadInstane
        }
    }
}

iteratorUploadObj(getActiveUploadObj, getFlashUploadObj, getFormUploadObj)

觀察上面經(jīng)過未使用迭代器模式,和使用迭代器模式后,發(fā)現(xiàn)不使用的話,如果后期想新增一個(gè)其他的模式,需要修改原來代碼的邏輯,如果使用迭代器的模式的話只需要新增一個(gè)方法即可。雖然在寫的時(shí)候代碼多了幾行,但總的來說,后期擴(kuò)展以及可閱讀性明顯提高了~。

單例模式
定義:單例模式指的是保證一個(gè)類僅有一個(gè)實(shí)例,且提供一個(gè)訪問它的全局訪問點(diǎn)。全局緩存,window對(duì)象,都可以看作是一個(gè)單例。

目的: 解決一個(gè)全局使用的類頻繁地創(chuàng)建與銷毀

使用ES6 class 創(chuàng)建單例:
class Instance {
    static init () {
        if (!this.instance) {
            this.instance = new Instance()
        }
        return this.instance;
    }
}
const instance1 = Instance.init()
const instance2 = Instance.init()

console.log(instance1 === instance2) //true
使用閉包創(chuàng)建單例:
const instance = (function() {
    let instance = null;
    return function(name) {
        if (!instance) {
            instance = new Singleton(name)
        }
        return instance;
    }
}())
使用代理實(shí)現(xiàn)單例模式

以在頁(yè)面上創(chuàng)建唯一的dom節(jié)點(diǎn)為例;

// 創(chuàng)建div類
class CreateDiv {
    constructor(html) {
        this.html = html
        this.init()
    }

    init() {
        let div = document.createElement("div");
        div.innerHTML = this.html;
        document.body.appendChild(div)
    }
}

// 代理類
class ProxySingletonCreateDiv {
    static initInstance(html) {
        if (!this.instance) {
            this.instance = new CreateDiv(html)
        }
        return this.instance;
    }
}

ProxySingletonCreateDiv.initInstance("test1");
ProxySingletonCreateDiv.initInstance("test2");

測(cè)試上面這段代碼會(huì)發(fā)現(xiàn)頁(yè)面上只會(huì)顯示test1,因?yàn)閷?shí)例只創(chuàng)建了一次,也就是new CreateDiv只執(zhí)行了第一次,第二次并沒有執(zhí)行。

惰性單例
惰性單例是指在需要的時(shí)候才創(chuàng)建對(duì)象實(shí)例。(在一定場(chǎng)景下,用戶只有在需要的時(shí)候才創(chuàng)建)

例:instance實(shí)例總是在我們調(diào)用getInstance的時(shí)候才會(huì)被創(chuàng)建

Singleton.getInstance = (function () {
    let instance = null;
    return function (name) {
        if (!instance) {
            instance = new Singleton(name)
        }
        return instance;
    }
}())

const instance = Singleton.getInstance("hello")

實(shí)現(xiàn)通用惰性單例

const getSingle = function (fn) {
    let result;
    return function() {
        return result || (result = fn.apply(this, arguments))
    }
}
裝飾者模式
裝飾者模式指的是:可以動(dòng)態(tài)地給某個(gè)對(duì)象添加一些額外的職責(zé),而不會(huì)影響從這個(gè)類中派生的其他對(duì)象。
最基礎(chǔ)的裝飾者

以編寫一個(gè)飛機(jī)大戰(zhàn)的游戲?yàn)槔S著等級(jí)的增加最開始我們只能發(fā)送普通的子彈,二級(jí)可以發(fā)送導(dǎo)彈,三級(jí)可以發(fā)送原子彈。

// 飛機(jī)對(duì)象
let plane = {
  fire: function () {
      console.log("發(fā)射普通子彈")
  }
};
// 發(fā)送導(dǎo)彈的方法,實(shí)際內(nèi)容略
let missileDecorator = function () {
    console.log("發(fā)射導(dǎo)彈");
};
// 發(fā)送原子彈的方法
let atomDecorator = function () {
    console.log("發(fā)射原子彈");
};
// 將發(fā)送子彈方法存起來,
let fire1 = plane.fire;
// 裝飾發(fā)送普通子彈的方法
plane.fire = function () {
    fire1();
    missileDecorator()
};

let fire2 = plane.fire;
plane.fire = function () {
    fire2();
    atomDecorator()
};

plane.fire() // 發(fā)射普通子彈,發(fā)射導(dǎo)彈,發(fā)射原子彈

我們有時(shí)候在維護(hù)代碼的時(shí)候,可能會(huì)遇到這樣的需求。比如給window綁定onload事件,但又不太確定這個(gè)事件是否被其他人綁定過了,為了避免覆蓋掉之前的window.onload的函數(shù)的行為,我們一般會(huì)向上面的例子一樣,將之前的行為保存在一個(gè)變量?jī)?nèi),然后再給綁定的window.onload函數(shù)添加這個(gè)變量執(zhí)行從而滿足需求。
以上方法的缺陷

需要多維護(hù)了中間變量,如上面的例子fire1fire2,如果鏈越來越長(zhǎng),那么維護(hù)的就越來越多。

還會(huì)遇到this劫持問題,如上fire函數(shù)被變量存起來的時(shí)候plane.fire執(zhí)行時(shí)this指向global(node環(huán)境)

AOP裝飾函數(shù)

為解決上面this的劫持問題,延伸實(shí)現(xiàn)Function.prototype.beforeFunction.prototype.after方法:

Function.prototype.before = function (beforeFn) {
    let that = this; // 保存原函數(shù)的引用
    return function () {
        beforeFn.apply(this, arguments);
        return that.apply(this, arguments); //執(zhí)行原函數(shù),且保證this不被劫持
    }
};

Function.prototype.after = function (afterFn) {
    let that = this;
    return function () {
        let ret = that.apply(this, arguments);
        afterFn.apply(this, arguments);
        return ret;
    }
};

改寫上面飛機(jī)的例子:

let plane = {
    fire: function () {
        console.log("發(fā)射普通子彈!")
    }
};
plane.fire.after(function () {
    console.log("發(fā)射導(dǎo)彈!")
}).after(function () {
    console.log("發(fā)射原子彈!")
})()

很明顯的看見解決了上面的兩個(gè)缺陷。

AOP應(yīng)用實(shí)例之?dāng)?shù)據(jù)上報(bào)

做前端開發(fā),主要提升用戶體驗(yàn),所以有時(shí)候在項(xiàng)目結(jié)尾為了能更多的收集到用戶的操作數(shù)據(jù)不得不加入一些埋點(diǎn)數(shù)據(jù)在業(yè)務(wù)中。如:點(diǎn)擊上報(bào)多少人點(diǎn)擊登錄按鈕來顯示登錄浮窗。

// 常規(guī)做法 bad
let log = function () {
    console.log("上報(bào)")// 實(shí)際內(nèi)容略
}
let showLogin = function () {
    console.log("打開登錄浮窗");
    log();
}
// 上面做法,showLogin既要做顯示彈窗的操作,又要負(fù)責(zé)數(shù)據(jù)上報(bào),違反了單一職責(zé)原則
// 使用裝飾者方式改寫 good
let showLogin1 = function () {
  console.log("顯示彈窗")
}

let showLogin = showLogin.after(log);
document.getElement("button").onclick = showLogin;

裝飾者模式與代碼模式的區(qū)別:
代理模式強(qiáng)調(diào)的是代理與它的實(shí)體之間的關(guān)系(這種關(guān)系在一開始就可以被確定),裝飾者模式用于一開始無法確定對(duì)象的全部功能場(chǎng)景。代理模式通常只有一層代理。而裝飾者會(huì)形成一條長(zhǎng)長(zhǎng)的裝飾鏈。

中介者模式
中介者指的是解除對(duì)象與對(duì)象之間的緊耦關(guān)系,增加中介者之后,所有對(duì)象通過中介者來通信,而不是互相引用,所以當(dāng)一個(gè)對(duì)象發(fā)生改變時(shí),只需要通知中介者對(duì)象即可。

現(xiàn)實(shí)生活中很的中介者場(chǎng)景,如:快遞物流公司,快遞員將負(fù)責(zé)的區(qū)域拿到用戶的快遞之后將快遞送到中轉(zhuǎn)場(chǎng),然后中轉(zhuǎn)場(chǎng)再進(jìn)行整理之后輸出分好類的區(qū)域,再由快遞員送到指定區(qū)域。在設(shè)計(jì)模式中,中轉(zhuǎn)場(chǎng)就扮演者,中介者的角色。

如圖將 A,B,C,D互相關(guān)聯(lián)的東西使用一個(gè)中介者進(jìn)行管理,減少A,B,C,D內(nèi)的互相引用。

const createAgent = (function () {
    return {
        add() => {
            //添加一個(gè)東西 代碼略
        },
        send () => {
            // 發(fā)送一個(gè)東西 代碼略
        }
    }
}())

const createA = function () {
    createAgent.add()
    setTimeout(() => {
        createAgent.send();
        
        // 代碼略
    }, 3000)
}

const createB = function () {
    //同上面方法類似
}

代碼只是說明中介者的意圖,內(nèi)容不要在意。

當(dāng)關(guān)聯(lián)的東西越來越多的時(shí)候中介者模式會(huì)變得越來越大,雖然會(huì)帶來這個(gè)缺點(diǎn),但取舍一下還是會(huì)比相互之間引用會(huì)更好。

發(fā)布-訂閱模式
發(fā)布-訂閱模式:定義對(duì)象之間的一種一對(duì)多的依賴關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對(duì)象都將得到通知!

常見的javascript事件指令,也是一種發(fā)布訂閱模式,

document.body.addEventListener("click", function () {
    // 一堆操作
}, false)

// 模擬用戶點(diǎn)擊
document.body.click()

這里監(jiān)控用戶點(diǎn)擊document.body的動(dòng)作,但我們并不知道用戶什么時(shí)候會(huì)點(diǎn)擊,所以我們訂閱document.body上的click事件,當(dāng)body被點(diǎn)擊后,body節(jié)點(diǎn)會(huì)向訂閱者發(fā)布這個(gè)消息。

發(fā)布與訂閱模式的實(shí)現(xiàn)步驟:

首先要指定誰(shuí)充當(dāng)發(fā)布者。

然后給發(fā)布者添加一個(gè)緩存列表,用于存放回調(diào)函數(shù)以便通知訂閱者

最后發(fā)布消息的時(shí)候,發(fā)布者會(huì)便利緩存列表依次觸發(fā)里面存放的訂閱者回調(diào)函數(shù)

以售樓處去購(gòu)買房子為例:A去售樓部查看了一下房源,并告知了銷售者小姐姐自己的電話以及自己需要的房源類型(這里A充當(dāng)訂閱者),售樓處將用戶的電話以及需要房源類型記錄在小本子上(這個(gè)小本子相當(dāng)于緩存列表),售樓處充當(dāng)發(fā)布者!下面以一段代碼實(shí)現(xiàn)這段描述:

// 定義售樓處
let saleOffices = {
    
    // 存放訂閱者的回調(diào)函數(shù),即用戶的電話以及需求
    clientList: {},
    
    // 訂閱消息
    listen: function (key, fn) {
        // 如果沒有訂閱過此類消息,則創(chuàng)建一個(gè)緩存列表
        if (!this.clientList[key]) { 
            this.clientList[key] = [];
        }
        // 將訂閱的消息添加進(jìn)消息緩存列表
        this.clientList[key].push(fn);
    },

    // 發(fā)布消息
    trigger: function () {
        let key = Array.prototype.shift.call(arguments );
        let fns = this.clientList[key];

        // 如果沒訂閱此消息則返回
        if (!fns || fns.length === 0) return false;


        for (let i = 0, fn; fn = fns[i++];) {
            // arguments是發(fā)布消息的附送參數(shù)
            fn.apply(this, arguments);
        }
    },
    
    // 取消訂閱
    remove: function (key, fn) {
        let fns = this.clientList[key];

        if (!fns) return false;

        // 如果沒有傳入回調(diào)函數(shù),則取消key所有的訂閱
        if (!fn) {
            fns && (fns.length = 0)
        } else {
            for (let l = fns.length - 1; l >= 0; l--) {
                let _fn = fns[l];
                if (_fn === fn) {
                    // 刪除訂閱的回調(diào)函數(shù)
                    fns.splice(l, 1)
                }
            }
        }
    }
}; 

let fn1 = function (price) {
    console.log("價(jià)格=", price)
}
saleOffices.listen("listen100", fn1)

saleOffices.remove("listen100", fn1)
saleOffices.trigger("listen100", 20000)

看了發(fā)布與訂閱模式和中介者模式,發(fā)現(xiàn)兩者之間有著很多相似之處。

發(fā)布-訂閱中介者之間的區(qū)別:

中介者目的是為了減少對(duì)象之間的耦合,而且類里的內(nèi)容可能存在著不同對(duì)象之間需要的一些東西存儲(chǔ),后期可能是某個(gè)對(duì)象自己去取。發(fā)布-訂閱主要也是解決對(duì)象之間的耦合,不同的是發(fā)布訂閱是取決用戶關(guān)注什么東西后發(fā)布者在有了這個(gè)東西之后主動(dòng)推送給訂閱者~
模板方法模式
模板模式指的是一種只需要使用繼承就可以實(shí)現(xiàn)的非常簡(jiǎn)單的模式 ,比較依賴于抽象類的一種設(shè)計(jì)模式,主要由抽象父類和具體實(shí)現(xiàn)子類組成!

抽象類可以表示一種契約,繼承了這個(gè)抽象類的所有子類都將擁有跟抽象類一致的接口方法,抽象類的主要作用就是為了它子類定義這些公共接口

咖啡與茶的例子

泡茶與沖咖啡:
首先可以將茶和咖啡抽象成飲料。

兩個(gè)在泡和沖都有相似的步驟:

把水煮沸

用沸水沖泡飲料

把飲料倒進(jìn)杯子

加調(diào)料

在類的繼承情況下,有時(shí)會(huì)存在子類未實(shí)現(xiàn)父類里已經(jīng)調(diào)用過的一些方法,常見解決可以在父類里添加對(duì)象的方法并提示一個(gè)錯(cuò)誤如:

Beverage.prototype.brew = function () {
    throw new Error("子類必須重寫brew方法");
}

實(shí)現(xiàn)沖咖啡與泡茶的例子:

// 抽象類
class Beverage {
    boilWater () {
        console.log("把水煮沸");
    }
    brew () {
        throw new Error("子類必須實(shí)現(xiàn)此方法!")
    }
    pourInCup () {
        throw new Error("子類必須實(shí)現(xiàn)此方法!")
    }
    addCondiments () {
        throw new Error("子類必須實(shí)現(xiàn)此方法!")
    }
    // 構(gòu)子方法是否需要添加調(diào)料
    customerWantsCondiments() {
        return true
    }
    init () {
        this.boilWater();
        this.brew();
        this.pourInCup();
        if (this.customerWantsCondiments()) {
            this.addCondiments();
        }
    }
}
// 咖啡類
class Coffee extends Beverage{
    constructor(props) {
        super(props)
    }

    brew() {
        console.log("用沸水煮咖啡")
    }
    pourInCup() {
        console.log("把咖啡倒進(jìn)杯子")
    }
    addCondiments() {
        console.log("加糖和牛奶")
    }
    customerWantsCondiments () {
        return false;
    }
}
// 泡茶類
class Tea extends Beverage {
    constructor(props) {
        super(props);
    }
    brew () {
        console.log("用沸水浸泡茶葉")
    }
    pourInCup () {
        console.log("將茶水倒進(jìn)杯子")
    }
    addCondiments () {
        console.log("加對(duì)應(yīng)配料")
    }
}
let coffee = new Coffee()
coffee.init() // 把水煮沸 用沸水煮咖啡 把咖啡倒進(jìn)杯子

let tea = new Tea()
tea.init(); // 把水煮沸 用沸水浸泡茶葉 將茶水倒進(jìn)杯子 加對(duì)應(yīng)配料
好萊塢原則
許多新人演員在好萊塢把簡(jiǎn)歷遞給演藝公司之后就只有回家等待盡管。有時(shí)候演員等得不耐煩了,給演藝公司打電話詢問情況,演藝公司往往這樣回答:“不要來差我,我會(huì)給你打電話。”,這就是好萊塢原則。

好萊塢原則,允許底層組件將自己掛鉤到高層組件中,而高層組件會(huì)決定什么時(shí)候,什么方式使用這些底層組件,與好萊塢原則一樣。

在javascript中,我們很多時(shí)候不需要用類這樣繁瑣的方式實(shí)現(xiàn)模板方法,使用高階函數(shù)更好。我們用高階函數(shù)改寫上面的例子。

    const Beverage = function(param) {
        let boilWater = function() {
            console.log("把水煮沸")
        }
        let brew = param.brew || function() {
            throw new Error("必須傳遞brew方法")
        }
        let pourInCup = param.pourInCup || function() {
            throw new Error("必須傳遞pourInCup方法")
        }
        let addCondiments = param.addCondiments || function() {
            throw new Error("必須傳遞addCondiments")
        }
        let F = function () {};
        F.prototype.init = function() {
            boilWater();
            brew();
            pourInCup();
            addCondiments()
        }
        return F;
    }
    const Coffee = Beverage({
        brew:function() {
            console.log("用沸水沖泡咖啡")
        },
        pourInCup: function() {
            console.log("把咖啡倒進(jìn)杯子")
        },
        addCondiments: function() {
            console.log("加糖和牛奶")
        }
    })
策略模式
策略模式指的是:定義一系列算法,把它們一個(gè)個(gè)封裝起來,并且使它們可以互相替換。

策略模式優(yōu)點(diǎn):

策略模式例用組合、委托和多態(tài)等技術(shù)和思想,可以有效地避免多重條件選擇語(yǔ)句

策略模式提供了對(duì)開放-封閉原則的完美支持,將算法封裝在獨(dú)立的strategy中,使得它們易于切換,易于理解,易于擴(kuò)展。

策略模式中的算法也可以復(fù)用在系統(tǒng)的其他地方,從而避免許多重復(fù)的復(fù)制粘貼工作。

在策略模式中利用組合和委托來讓context擁有執(zhí)行算法的能力,這也是繼承的一種更輕便的替代方案。

表單校驗(yàn)例子

校驗(yàn)邏輯:

用戶名不能為空,用戶名長(zhǎng)度不能小于10位

密碼長(zhǎng)度不能少于6位

手機(jī)號(hào)必須符合格式。

為了下面的javascript代碼更少的編寫html代碼,我這里將html代碼提取出來

// html 代碼

    
        
請(qǐng)輸入用戶名: 請(qǐng)輸入密碼: 請(qǐng)輸入手機(jī)號(hào):

不使用策略模式我們正常的實(shí)現(xiàn)

 const registerForm = document.getElementById("registerForm");
 registerForm.onsubmit = function() {
     const userName = registerForm.userName.value
     if(userName === "" && userName.length >= 10) {
         console.log("用戶名不能為空")
        return false;
     }
     if (registerForm.password.value.length < 6) {
         console.log("密碼不能為空")
         return false;
     }

     if (!/(^1[3|5|8][0|9]{9}$)/.test(registerForm.phoneNumber.value)) {
         console.log("手機(jī)號(hào)輸入不正確")
         return false;
     }
 }

這樣的代碼會(huì)導(dǎo)致校驗(yàn)的函數(shù)越來越龐大,在系統(tǒng)變化的時(shí)候缺乏彈性。

使用策略模式重構(gòu)上面的表單校驗(yàn):

策略模式的組成部分:

策略類:封裝具體算法,并負(fù)責(zé)具體計(jì)算過程。

環(huán)境類:環(huán)境類Context,Context接受客戶的請(qǐng)求,隨后把請(qǐng)求委托給某一個(gè)策略類。

// 封裝算法
const strategies = {
    isNonEmpty: function (value, errorMsg) {
        if (value === "") {
            return errorMsg;
        }
    },
    minLength: function (value, length, errorMsg) {
        if (value.length < length) {
            return errorMsg;
        }
    },
    isMobile: function (value, errorMsg) {
        if (!/(^1[3|5|8][0|9]{9}$)/.test(value)) {
            return errorMsg;
        }
    }
}

// 實(shí)現(xiàn)Context環(huán)境類
const Validator = function () {
    this.cache = [];
}
Validator.prototype = {
    add (dom, rules) {
        let self = this;
        for (let i = 0, rule; rule = rules[i++];) {
            let strategyAry = rule.strategy.split(":");
            let errorMsg = rule.errorMsg;

            self.cache.push(function () {
                let strategy = strategyAry.shift()
                strategyAry.unshift(dom.value)
                strategyAry.push(errorMsg);
                return strategies[strategy].apply(dom, strategyAry)
            })
        }
    },
    start () {
        for(let i = 0, validatorFunc; validatorFunc = this.cache[i++];) {
            let errorMsg = validatorFunc();
            if (errorMsg) {
                return errorMsg;
            }
        }
    }
}

// 客戶端使用
let registerForm = document.getElementById("registerForm");
let validataFunc = function () {
    let validator = new Validator();
    validator.add(registerForm.userName, [{
        strategy: "isNonEmpty",
        errorMsg: "用戶名不能為空"
    }, {
        strategy: "minLength:10",
        errorMsg: "用戶名長(zhǎng)度不能小于10位"
    }])

    validator.add(registerForm.password, [{
        strategy: "minLength:6",
        errorMsg: "密碼長(zhǎng)度不能小于6位"
    }])
    
    validator.add(registerForm.phoneNumber, [{
        strategy: "isMobile",
        errorMsg: "手機(jī)號(hào)碼格式不正確"
    }])
    let errorMsg = validator.start();
    return errorMsg;
}

registerForm.onsubmit = function () {
    let errorMsg = validataFunc();
    if (errorMsg) {
        console.log("errorMsg");
        return false;
    }
}

雖然看起來代碼多了很多,但對(duì)于以后的維護(hù)和擴(kuò)展方法,復(fù)用方法,這種方式明顯會(huì)好很多。

職責(zé)鏈模式
職責(zé)鏈模式指的是:使多個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求,從而避免請(qǐng)求的發(fā)送者和接收者之間的耦合關(guān)系,將這些對(duì)象連成一個(gè)鏈條,并沿著鏈條傳遞這些請(qǐng)求,直到有一個(gè)對(duì)象處理它為止。

職責(zé)鏈生活例子:

中學(xué)時(shí)期末考試,如果你平時(shí)不老實(shí)考試時(shí)就會(huì)被安排到第一排的位置,遇到不會(huì)答的題就會(huì)把題寫在紙條上傳給后排的同學(xué),如果后排同樣不會(huì)繼續(xù)往后排傳值到會(huì)為止。

實(shí)際開發(fā)中職責(zé)鏈模式使用場(chǎng)景:

需求是這樣的:

商場(chǎng)在預(yù)定手機(jī)時(shí),分別繳納500和200的定金,到的購(gòu)買階段后交付500定金的可以收到100優(yōu)惠券,200的可以收到50優(yōu)惠券,沒有支付定金沒有優(yōu)惠券且只能保證購(gòu)買時(shí)庫(kù)存有貨時(shí)能購(gòu)買成功!

平常我們拿到這樣的需求后可能會(huì)寫下面這樣的代碼:

const fn = function(stock) {
    if (stock > 0) {
        console.log("普通購(gòu)買,無優(yōu)惠券")
    } else {
        console.log("手機(jī)庫(kù)存不足")
    }
}
/**
 * @param {number} orderType 訂單類型1,2,3
 * @param {boolean} pay 是否支付定金 true | false
 * @param {number} stock 庫(kù)存數(shù)量 
 */
let order = function(orderType, pay, stock) {
    if (orderType === 1) {
        if (pay === true) {
            console.log("500元定金預(yù)購(gòu),得到100優(yōu)惠券。")
        } else {
            fn(stock);
        }
    } else if (olderType === 2) {
        if (pay === true) {
            console.log("200元,50優(yōu)惠券")
        } else {
            fn(stock)
        }
    } else if (orderType === 3) {
        fn(stock)
    }
}

看上面的代碼,邏輯上也沒什么問題,但相信我們?cè)趯懙臅r(shí)候一般也不會(huì)這樣去寫,因?yàn)檫@樣在后期維護(hù)的時(shí)候order函數(shù)會(huì)變得越來越龐大,而且要新增一些其他的邏輯也是比較困難。

使用職責(zé)鏈模式重寫上面的例子:

拆分條件語(yǔ)句,將每個(gè)條件提取成一個(gè)函數(shù)。

約定一個(gè)字符串"nextSuccess"是否需要向后傳遞

包裝職責(zé)鏈chain

const order500 = function(orderType, pay, stock) {
    if(orderType === 1 && pay === true) {
        console.log("500定金,100優(yōu)惠券")
    } else {
        return "nextSuccess"
    }
}

const order200 = function(orderType, pay, stock) {
    if (orderType === 2 && pay === true) {
        console.log("200定金,返50優(yōu)惠券")
    } else {
        return "nextSuccess"
    }
}

const orderNormal = function(orderType, pay, stock) {
    if (stock > 0) {
        console.log("普通購(gòu)買")
    } else {
        console.log("手機(jī)庫(kù)存不足")
    }
}

const Chain = function(fn) {
    this.fn = fn;
    this.successor = null;
}

Chain.prototype = {
    setNextSuccessor: function(successor) {
        return this.successor = successor;
    },
    passRequest: function() {
        let ret = this.fn.apply(this, arguments);
        
        if (ret === "nextSuccessor") {
            return this.successor && this.successor.passRequest.apply(this.successor, arguments)
        }

        return ret;
    }
}
// 包裝職責(zé)鏈節(jié)點(diǎn)
const chainOrder500 = new Chain(order500);
const chainOrder200 = new Chain(order200);
const chainOrderNormal = new Chain(orderNormal);
// 指定職責(zé)鏈順序
chainOrder500.setNextSuccessor(chainOrder200);
chainOrder200.setNextSuccessor(chainOrderNormal)
// 應(yīng)用
chainOrder500.passRequest(1, true, 500); // 500定金,100優(yōu)惠券
chainOrder500.passRequest(1, false, 0) // 庫(kù)存不足

職責(zé)鏈的缺點(diǎn):

職責(zé)鏈?zhǔn)钩绦蛑卸嗔艘恍┕?jié)點(diǎn)對(duì)象,可能在某一次的請(qǐng)求傳遞過程中,大部分節(jié)點(diǎn)沒有起到實(shí)質(zhì)性的作用,它們的作用僅僅讓請(qǐng)求傳遞下去,從性能方面考慮我們應(yīng)該避免過長(zhǎng)的職責(zé)鏈帶來的性能損耗。

在之前我們使用了AOP裝飾函數(shù),實(shí)現(xiàn)裝飾者的模式。
同樣 這里我們可以使用AOP實(shí)現(xiàn)職責(zé)鏈

改寫之前的Function.prototype.after函數(shù)

Function.prototype.after = function(fn) {
    let self = this;
    return function() {
        let ret = self.apply(this, arguments);
        
        if (ret === "nextSuccessor") {
            return fn.apply(this, arguments)
        }
        return ret;
    }
}
// 指定順序
let order = order500.after(order200).after(orderNormal);
order(1, true,  50) // 500定金,100優(yōu)惠券

去掉了chain類,整個(gè)邏輯也變得更加清晰了,同樣這種方式也不適合太長(zhǎng)的鏈條。

狀態(tài)模式
狀態(tài)模式指的是:允許一個(gè)對(duì)象在其內(nèi)部狀態(tài)改變時(shí)改變它的行為,對(duì)象看起來似乎修改了它的類。

狀態(tài)模式的優(yōu)點(diǎn):

狀態(tài)模式定義了狀態(tài)與行為之間的關(guān)系,并將它們封裝在一個(gè)類里。通過增加新的狀態(tài)類,很容易增加新的狀態(tài)和轉(zhuǎn)換。

避免Context無限膨脹,狀態(tài)切換的邏輯被分布在狀態(tài)類中,也去掉了Context中原來過多的條件分支。

用對(duì)象代替字符串來記錄當(dāng)前狀態(tài),使得狀態(tài)的切換更加一目了然。

Context中的請(qǐng)求動(dòng)作和狀態(tài)類中封裝的行為可以非常容易地獨(dú)立變化而互不影響。

使用javascript版本的狀態(tài)機(jī),實(shí)現(xiàn)開燈與關(guān)燈例子:

let delegate = function(client, delegation) {
    return {
        buttonWasPressed: function() {
            // 將客戶端操作委托給delegation對(duì)象
            return delegation.buttonWasPressed.apply(client, arguments)
        }
    }
}
let FSM = {
    off: {
        buttonWasPressed: function() {
            console.log("關(guān)燈")
            this.button.innerHTML = "下次按我是開燈";
            this.currState = this.onState;
        }
    }, 
    on: {
        buttonWasPressed: function() {
            console.log("開燈");
            this.button.innerHTML = "下次按我是關(guān)燈"
            this.currState = this.offState;
        }
    }
}

let Light = function() {
    this.offState = delegate(this, FSM.off);
    this.onState = delegate(this, FSM.on);
    this.currState = this.offState; // 設(shè)置初始狀態(tài)為關(guān)閉狀態(tài)
    this.button = null;
}

Light.prototype = {
    init () {
        let button = document.getElementById("button");
        let self = this;
        button.innerHTML = "已關(guān)燈";
        this.button = document.body.appendChild(button);
        this.button.onclick = function () {
            self.currState.buttonWasPressed()
        }
    }
}
let light = new Light()
light.init()
結(jié)語(yǔ)

寫這篇文章主要意圖在于以前也看過javascript設(shè)計(jì)模式這本書,但可能在寫代碼的時(shí)候一般只會(huì)想到文章開頭的三個(gè)原則,但具體如何通過比較優(yōu)雅的代碼去滿足三個(gè)原則是比較困難的,最近又重新看了一遍這本書,為了加深自己的印象,將一些比較常用的模式通過自己去描述的方式呈現(xiàn)出來,也能分享給廣大的搬磚同學(xué)~, 如果覺得讀完文章后有所收獲,請(qǐng)點(diǎn)贊和收藏給予一丟丟鼓勵(lì),haha

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

轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/106954.html

相關(guān)文章

  • 個(gè)人分享--web前端學(xué)習(xí)資源分享

    摘要:前言月份開始出沒社區(qū),現(xiàn)在差不多月了,按照工作的說法,就是差不多過了三個(gè)月的試用期,準(zhǔn)備轉(zhuǎn)正了一般來說,差不多到了轉(zhuǎn)正的時(shí)候,會(huì)進(jìn)行總結(jié)或者分享會(huì)議那么今天我就把看過的一些學(xué)習(xí)資源主要是博客,博文推薦分享給大家。 1.前言 6月份開始出沒社區(qū),現(xiàn)在差不多9月了,按照工作的說法,就是差不多過了三個(gè)月的試用期,準(zhǔn)備轉(zhuǎn)正了!一般來說,差不多到了轉(zhuǎn)正的時(shí)候,會(huì)進(jìn)行總結(jié)或者分享會(huì)議!那么今天我就...

    sherlock221 評(píng)論0 收藏0
  • 前端資源系列(4)-前端學(xué)習(xí)資源分享&前端面試資源匯總

    摘要:特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點(diǎn)多,可以很快搞定,沒想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補(bǔ)充。有錯(cuò)誤的地方,還請(qǐng)斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會(huì)及時(shí)更新,平時(shí)業(yè)務(wù)工作時(shí)也會(huì)不定期更...

    princekin 評(píng)論0 收藏0
  • 前端每周清單半年盤點(diǎn)之 JavaScript

    摘要:前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點(diǎn)分為新聞熱點(diǎn)開發(fā)教程工程實(shí)踐深度閱讀開源項(xiàng)目巔峰人生等欄目。背后的故事本文是對(duì)于年之間世界發(fā)生的大事件的詳細(xì)介紹,闡述了從提出到角力到流產(chǎn)的前世今生。 前端每周清單專注前端領(lǐng)域內(nèi)容,以對(duì)外文資料的搜集為主,幫助開發(fā)者了解一周前端熱點(diǎn);分為新聞熱點(diǎn)、開發(fā)教程、工程實(shí)踐、深度閱讀、開源項(xiàng)目、巔峰人生等欄目。歡迎...

    Vixb 評(píng)論0 收藏0
  • JavasScript重難點(diǎn)知識(shí)

    摘要:忍者級(jí)別的函數(shù)操作對(duì)于什么是匿名函數(shù),這里就不做過多介紹了。我們需要知道的是,對(duì)于而言,匿名函數(shù)是一個(gè)很重要且具有邏輯性的特性。通常,匿名函數(shù)的使用情況是創(chuàng)建一個(gè)供以后使用的函數(shù)。 JS 中的遞歸 遞歸, 遞歸基礎(chǔ), 斐波那契數(shù)列, 使用遞歸方式深拷貝, 自定義事件添加 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果...

    forsigner 評(píng)論0 收藏0
  • 一名【合格】前端工程師自檢清單

    摘要:在他的重學(xué)前端課程中提到到現(xiàn)在為止,前端工程師已經(jīng)成為研發(fā)體系中的重要崗位之一。大部分前端工程師的知識(shí),其實(shí)都是來自于實(shí)踐和工作中零散的學(xué)習(xí)。一基礎(chǔ)前端工程師吃飯的家伙,深度廣度一樣都不能差。 開篇 前端開發(fā)是一個(gè)非常特殊的行業(yè),它的歷史實(shí)際上不是很長(zhǎng),但是知識(shí)之繁雜,技術(shù)迭代速度之快是其他技術(shù)所不能比擬的。 winter在他的《重學(xué)前端》課程中提到: 到現(xiàn)在為止,前端工程師已經(jīng)成為研...

    羅志環(huán) 評(píng)論0 收藏0

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

0條評(píng)論

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