亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

一文總結(jié)JavaScript中常見(jiàn)的設(shè)計(jì)模式

 更新時(shí)間:2023年05月06日 09:13:08   作者:白哥學(xué)前端  
在程序設(shè)計(jì)中有很多實(shí)用的設(shè)計(jì)模式,而其中大部分語(yǔ)言的實(shí)現(xiàn)都是基于“類(lèi)”。在程序設(shè)計(jì)中有很多實(shí)用的設(shè)計(jì)模式,而其中大部分語(yǔ)言的實(shí)現(xiàn)都是基于“類(lèi)”。,本文將總結(jié)了JavaScript中常見(jiàn)的十五種設(shè)計(jì)模式,感興趣的朋友可以參考下

在程序設(shè)計(jì)中有很多實(shí)用的設(shè)計(jì)模式,而其中大部分語(yǔ)言的實(shí)現(xiàn)都是基于“類(lèi)”。

在JavaScript中并沒(méi)有類(lèi)這種概念,JS中的函數(shù)屬于一等對(duì)象,在JS中定義一個(gè)對(duì)象非常簡(jiǎn)單(var obj = {}),而基于JS中閉包與弱類(lèi)型等特性,在實(shí)現(xiàn)一些設(shè)計(jì)模式的方式上與眾不同。

本文基于《JavaScript設(shè)計(jì)模式與開(kāi)發(fā)實(shí)踐》一書(shū),用一些例子總結(jié)一下JS常見(jiàn)的設(shè)計(jì)模式與實(shí)現(xiàn)方法。文章略長(zhǎng),自備瓜子板凳~

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

單一職責(zé)原則(SRP)

一個(gè)對(duì)象或方法只做一件事情。如果一個(gè)方法承擔(dān)了過(guò)多的職責(zé),那么在需求的變遷過(guò)程中,需要改寫(xiě)這個(gè)方法的可能性就越大。

應(yīng)該把對(duì)象或方法劃分成較小的粒度

最少知識(shí)原則(LKP)

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

應(yīng)當(dāng)盡量減少對(duì)象之間的交互。如果兩個(gè)對(duì)象之間不必彼此直接通信,那么這兩個(gè)對(duì)象就不要發(fā)生直接的 相互聯(lián)系,可以轉(zhuǎn)交給第三方進(jìn)行處理

開(kāi)放-封閉原則(OCP)

軟件實(shí)體(類(lèi)、模塊、函數(shù))等應(yīng)該是可以 擴(kuò)展的,但是不可修改

當(dāng)需要改變一個(gè)程序的功能或者給這個(gè)程序增加新功能的時(shí)候,可以使用增加代碼的方式,盡量避免改動(dòng)程序的源代碼,防止影響原系統(tǒng)的穩(wěn)定

什么是設(shè)計(jì)模式

作者的這個(gè)說(shuō)明解釋得挺好

假設(shè)有一個(gè)空房間,我們要日復(fù)一日地往里 面放一些東西。最簡(jiǎn)單的辦法當(dāng)然是把這些東西 直接扔進(jìn)去,但是時(shí)間久了,就會(huì)發(fā)現(xiàn)很難從這 個(gè)房子里找到自己想要的東西,要調(diào)整某幾樣?xùn)| 西的位置也不容易。所以在房間里做一些柜子也 許是個(gè)更好的選擇,雖然柜子會(huì)增加我們的成 本,但它可以在維護(hù)階段為我們帶來(lái)好處。使用 這些柜子存放東西的規(guī)則,或許就是一種模式

學(xué)習(xí)設(shè)計(jì)模式,有助于寫(xiě)出可復(fù)用和可維護(hù)性高的程序

設(shè)計(jì)模式的原則是“找出 程序中變化的地方,并將變化封裝起來(lái)”,它的關(guān)鍵是意圖,而不是結(jié)構(gòu)。

不過(guò)要注意,使用不當(dāng)?shù)脑挘赡軙?huì)事倍功半。

一、單例模式

1. 定義

保證一個(gè)類(lèi)僅有一個(gè)實(shí)例,并提供一個(gè)訪問(wèn)它的全局訪問(wèn)點(diǎn)

2. 核心

確保只有一個(gè)實(shí)例,并提供全局訪問(wèn)

3. 實(shí)現(xiàn)

假設(shè)要設(shè)置一個(gè)管理員,多次調(diào)用也僅設(shè)置一次,我們可以使用閉包緩存一個(gè)內(nèi)部變量來(lái)實(shí)現(xiàn)這個(gè)單例

function SetManager(name) {
    this.manager = name;
}
SetManager.prototype.getName = function() {
    console.log(this.manager);
};
var SingletonSetManager = (function() {
    var manager = null;
    return function(name) {
        if (!manager) {
            manager = new SetManager(name);
        }
        return manager;
    } 
})();
SingletonSetManager('a').getName(); // a
SingletonSetManager('b').getName(); // a
SingletonSetManager('c').getName(); // a

這是比較簡(jiǎn)單的做法,但是假如我們還要設(shè)置一個(gè)HR呢?就得復(fù)制一遍代碼了

所以,可以改寫(xiě)單例內(nèi)部,實(shí)現(xiàn)地更通用一些

// 提取出通用的單例
function getSingleton(fn) {
    var instance = null;
    return function() {
        if (!instance) {
            instance = fn.apply(this, arguments);
        }
        return instance;
    }
}

再進(jìn)行調(diào)用,結(jié)果還是一樣

// 獲取單例
var managerSingleton = getSingleton(function(name) {
    var manager = new SetManager(name);
    return manager;
});
managerSingleton('a').getName(); // a
managerSingleton('b').getName(); // a
managerSingleton('c').getName(); // a

這時(shí),我們添加HR時(shí),就不需要更改獲取單例內(nèi)部的實(shí)現(xiàn)了,僅需要實(shí)現(xiàn)添加HR所需要做的,再調(diào)用即可

function SetHr(name) {
    this.hr = name;
}
SetHr.prototype.getName = function() {
    console.log(this.hr);
};
var hrSingleton = getSingleton(function(name) {
    var hr = new SetHr(name);
    return hr;
});
hrSingleton('aa').getName(); // aa
hrSingleton('bb').getName(); // aa
hrSingleton('cc').getName(); // aa

或者,僅想要?jiǎng)?chuàng)建一個(gè)div層,不需要將對(duì)象實(shí)例化,直接調(diào)用函數(shù)

結(jié)果為頁(yè)面中僅有第一個(gè)創(chuàng)建的div

function createPopup(html) {
    var div = document.createElement('div');
    div.innerHTML = html;
    document.body.append(div);
    return div;
}
var popupSingleton = getSingleton(function() {
    var div = createPopup.apply(this, arguments);
    return div;
});
console.log(
    popupSingleton('aaa').innerHTML,
    popupSingleton('bbb').innerHTML,
    popupSingleton('bbb').innerHTML
); // aaa  aaa  aaa

二、策略模式

1. 定義

定義一系列的算法,把它們一個(gè)個(gè)封裝起來(lái),并且使它們可以相互替換。

2. 核心

將算法的使用和算法的實(shí)現(xiàn)分離開(kāi)來(lái)。

一個(gè)基于策略模式的程序至少由兩部分組成:

第一個(gè)部分是一組策略類(lèi),策略類(lèi)封裝了具體的算法,并負(fù)責(zé)具體的計(jì)算過(guò)程。

第二個(gè)部分是環(huán)境類(lèi)Context,Context接受客戶的請(qǐng)求,隨后把請(qǐng)求委托給某一個(gè)策略類(lèi)。要做到這點(diǎn),說(shuō)明Context 中要維持對(duì)某個(gè)策略對(duì)象的引用

3. 實(shí)現(xiàn)

策略模式可以用于組合一系列算法,也可用于組合一系列業(yè)務(wù)規(guī)則

假設(shè)需要通過(guò)成績(jī)等級(jí)來(lái)計(jì)算學(xué)生的最終得分,每個(gè)成績(jī)等級(jí)有對(duì)應(yīng)的加權(quán)值。我們可以利用對(duì)象字面量的形式直接定義這個(gè)組策略

// 加權(quán)映射關(guān)系
var levelMap = {
    S: 10,
    A: 8,
    B: 6,
    C: 4
};
// 組策略
var scoreLevel = {
    basicScore: 80,
    S: function() {
        return this.basicScore + levelMap['S']; 
    },
    A: function() {
        return this.basicScore + levelMap['A']; 
    },
    B: function() {
        return this.basicScore + levelMap['B']; 
    },
    C: function() {
        return this.basicScore + levelMap['C']; 
    }
}
// 調(diào)用
function getScore(level) {
    return scoreLevel[level] ? scoreLevel[level]() : 0;
}
console.log(
    getScore('S'),
    getScore('A'),
    getScore('B'),
    getScore('C'),
    getScore('D')
); // 90 88 86 84 0

在組合業(yè)務(wù)規(guī)則方面,比較經(jīng)典的是表單的驗(yàn)證方法。這里列出比較關(guān)鍵的部分

// 錯(cuò)誤提示
var errorMsgs = {
    default: '輸入數(shù)據(jù)格式不正確',
    minLength: '輸入數(shù)據(jù)長(zhǎng)度不足',
    isNumber: '請(qǐng)輸入數(shù)字',
    required: '內(nèi)容不為空'
};
// 規(guī)則集
var rules = {
    minLength: function(value, length, errorMsg) {
        if (value.length < length) {
            return errorMsg || errorMsgs['minLength']
        }
    },
    isNumber: function(value, errorMsg) {
        if (!/\d+/.test(value)) {
            return errorMsg || errorMsgs['isNumber'];
        }
    },
    required: function(value, errorMsg) {
        if (value === '') {
            return errorMsg || errorMsgs['required'];
        }
    }
};
// 校驗(yàn)器
function Validator() {
    this.items = [];
};
Validator.prototype = {
    constructor: Validator,
    
    // 添加校驗(yàn)規(guī)則
    add: function(value, rule, errorMsg) {
        var arg = [value];
        if (rule.indexOf('minLength') !== -1) {
            var temp = rule.split(':');
            arg.push(temp[1]);
            rule = temp[0];
        }
        arg.push(errorMsg);
        this.items.push(function() {
            // 進(jìn)行校驗(yàn)
            return rules[rule].apply(this, arg);
        });
    },
    
    // 開(kāi)始校驗(yàn)
    start: function() {
        for (var i = 0; i < this.items.length; ++i) {
            var ret = this.items[i]();
            
            if (ret) {
                console.log(ret);
                // return ret;
            }
        }
    }
};
// 測(cè)試數(shù)據(jù)
function testTel(val) {
    return val;
}
var validate = new Validator();
validate.add(testTel('ccc'), 'isNumber', '只能為數(shù)字'); // 只能為數(shù)字
validate.add(testTel(''), 'required'); // 內(nèi)容不為空
validate.add(testTel('123'), 'minLength:5', '最少5位'); // 最少5位
validate.add(testTel('12345'), 'minLength:5', '最少5位');
var ret = validate.start();
console.log(ret);

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

優(yōu)點(diǎn)

可以有效地避免多重條件語(yǔ)句,將一系列方法封裝起來(lái)也更直觀,利于維護(hù)

缺點(diǎn)

往往策略集會(huì)比較多,我們需要事先就了解定義好所有的情況

三、代理模式

1. 定義

為一個(gè)對(duì)象提供一個(gè)代用品或占位符,以便控制對(duì)它的訪問(wèn)

2. 核心

當(dāng)客戶不方便直接訪問(wèn)一個(gè) 對(duì)象或者不滿足需要的時(shí)候,提供一個(gè)替身對(duì)象 來(lái)控制對(duì)這個(gè)對(duì)象的訪問(wèn),客戶實(shí)際上訪問(wèn)的是 替身對(duì)象。

替身對(duì)象對(duì)請(qǐng)求做出一些處理之后, 再把請(qǐng)求轉(zhuǎn)交給本體對(duì)象

代理和本體的接口具有一致性,本體定義了關(guān)鍵功能,而代理是提供或拒絕對(duì)它的訪問(wèn),或者在訪問(wèn)本體之前做一 些額外的事情

3. 實(shí)現(xiàn)

代理模式主要有三種:保護(hù)代理、虛擬代理、緩存代理

保護(hù)代理主要實(shí)現(xiàn)了訪問(wèn)主體的限制行為,以過(guò)濾字符作為簡(jiǎn)單的例子

// 主體,發(fā)送消息
function sendMsg(msg) {
    console.log(msg);
}
// 代理,對(duì)消息進(jìn)行過(guò)濾
function proxySendMsg(msg) {
    // 無(wú)消息則直接返回
    if (typeof msg === 'undefined') {
        console.log('deny');
        return;
    }
    
    // 有消息則進(jìn)行過(guò)濾
    msg = ('' + msg).replace(/泥\s*煤/g, '');
    sendMsg(msg);
}

sendMsg('泥煤呀泥 煤呀'); // 泥煤呀泥 煤呀

proxySendMsg('泥煤呀泥 煤'); // 呀

proxySendMsg(); // deny

它的意圖很明顯,在訪問(wèn)主體之前進(jìn)行控制,沒(méi)有消息的時(shí)候直接在代理中返回了,拒絕訪問(wèn)主體,這數(shù)據(jù)保護(hù)代理的形式

有消息的時(shí)候?qū)γ舾凶址M(jìn)行了處理,這屬于虛擬代理的模式

虛擬代理在控制對(duì)主體的訪問(wèn)時(shí),加入了一些額外的操作

在滾動(dòng)事件觸發(fā)的時(shí)候,也許不需要頻繁觸發(fā),我們可以引入函數(shù)節(jié)流,這是一種虛擬代理的實(shí)現(xiàn)

// 函數(shù)防抖,頻繁操作中不處理,直到操作完成之后(再過(guò) delay 的時(shí)間)才一次性處理
function debounce(fn, delay) {
    delay = delay || 200;
    
    var timer = null;
    return function() {
        var arg = arguments;
          
        // 每次操作時(shí),清除上次的定時(shí)器
        clearTimeout(timer);
        timer = null;
        
        // 定義新的定時(shí)器,一段時(shí)間后進(jìn)行操作
        timer = setTimeout(function() {
            fn.apply(this, arg);
        }, delay);
    }
};
var count = 0;
// 主體
function scrollHandle(e) {
    console.log(e.type, ++count); // scroll
}
// 代理
var proxyScrollHandle = (function() {
    return debounce(scrollHandle, 500);
})();
window.onscroll = proxyScrollHandle;

緩存代理可以為一些開(kāi)銷(xiāo)大的運(yùn)算結(jié)果提供暫時(shí)的緩存,提升效率

來(lái)個(gè)栗子,緩存加法操作

// 主體
function add() {
    var arg = [].slice.call(arguments);
    return arg.reduce(function(a, b) {
        return a + b;
    });
}
// 代理
var proxyAdd = (function() {
    var cache = [];
    return function() {
        var arg = [].slice.call(arguments).join(',');
        
        // 如果有,則直接從緩存返回
        if (cache[arg]) {
            return cache[arg];
        } else {
            var ret = add.apply(this, arguments);
            return ret;
        }
    };
})();
console.log(
    add(1, 2, 3, 4),
    add(1, 2, 3, 4),
    proxyAdd(10, 20, 30, 40),
    proxyAdd(10, 20, 30, 40)
); // 10 10 100 100

四、迭代器模式

1. 定義

迭代器模式是指提供一種方法順序訪問(wèn)一個(gè)聚合對(duì)象中的各個(gè)元素,而又不需要暴露該對(duì)象的內(nèi)部表示。

2. 核心

在使用迭代器模式之后,即使不關(guān)心對(duì)象的內(nèi)部構(gòu)造,也可以按順序訪問(wèn)其中的每個(gè)元素

3. 實(shí)現(xiàn)

JS中數(shù)組的map forEach 已經(jīng)內(nèi)置了迭代器

[1, 2, 3].forEach(function(item, index, arr) {
    console.log(item, index, arr);
});

不過(guò)對(duì)于對(duì)象的遍歷,往往不能與數(shù)組一樣使用同一的遍歷代碼

我們可以封裝一下

function each(obj, cb) {
    var value;
    if (Array.isArray(obj)) {
        for (var i = 0; i < obj.length; ++i) {
            value = cb.call(obj[i], i, obj[i]);
            if (value === false) {
                break;
            }
        }
    } else {
        for (var i in obj) {
            value = cb.call(obj[i], i, obj[i]);
            if (value === false) {
                break;
            }
        }
    }
}
each([1, 2, 3], function(index, value) {
    console.log(index, value);
});
each({a: 1, b: 2}, function(index, value) {
    console.log(index, value);
});
// 0 1
// 1 2
// 2 3
// a 1
// b 2

再來(lái)看一個(gè)例子,強(qiáng)行地使用迭代器,來(lái)了解一下迭代器也可以替換頻繁的條件語(yǔ)句

雖然例子不太好,但在其他負(fù)責(zé)的分支判斷情況下,也是值得考慮的

function getManager() {
    var year = new Date().getFullYear();
    if (year <= 2000) {
        console.log('A');
    } else if (year >= 2100) {
        console.log('C');
    } else {
        console.log('B');
    }
}
getManager(); // B

將每個(gè)條件語(yǔ)句拆分出邏輯函數(shù),放入迭代器中迭代

function year2000() {
    var year = new Date().getFullYear();
    if (year <= 2000) {
        console.log('A');
    }
    return false;
}
function year2100() {
    var year = new Date().getFullYear();
    if (year >= 2100) {
        console.log('C');
    }
    return false;
}
function year() {
    var year = new Date().getFullYear();
    if (year > 2000 && year < 2100) {
        console.log('B');
    }
    return false;
}
function iteratorYear() {
    for (var i = 0; i < arguments.length; ++i) {
        var ret = arguments[i]();
        if (ret !== false) {
            return ret;
        }
    }
}
var manager = iteratorYear(year2000, year2100, year); // B

五、發(fā)布-訂閱模式

1. 定義

也稱作觀察者模式,定義了對(duì)象間的一種一對(duì)多的依賴關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā) 生改變時(shí),所有依賴于它的對(duì)象都將得到通知

2. 核心

取代對(duì)象之間硬編碼的通知機(jī)制,一個(gè)對(duì)象不用再顯式地調(diào)用另外一個(gè)對(duì)象的某個(gè)接口。

與傳統(tǒng)的發(fā)布-訂閱模式實(shí)現(xiàn)方式(將訂閱者自身當(dāng)成引用傳入發(fā)布者)不同,在JS中通常使用注冊(cè)回調(diào)函數(shù)的形式來(lái)訂閱

3. 實(shí)現(xiàn)

JS中的事件就是經(jīng)典的發(fā)布-訂閱模式的實(shí)現(xiàn)

// 訂閱
document.body.addEventListener('click', function() {
    console.log('click1');
}, false);
document.body.addEventListener('click', function() {
    console.log('click2');
}, false);
// 發(fā)布
document.body.click(); // click1  click2

自己實(shí)現(xiàn)一下

小A在公司C完成了筆試及面試,小B也在公司C完成了筆試。他們焦急地等待結(jié)果,每隔半天就電話詢問(wèn)公司C,導(dǎo)致公司C很不耐煩。

一種解決辦法是 AB直接把聯(lián)系方式留給C,有結(jié)果的話C自然會(huì)通知AB

這里的“詢問(wèn)”屬于顯示調(diào)用,“留給”屬于訂閱,“通知”屬于發(fā)布

// 觀察者
var observer = {
    // 訂閱集合
    subscribes: [],
    // 訂閱
    subscribe: function(type, fn) {
        if (!this.subscribes[type]) {
            this.subscribes[type] = [];
        }
        
        // 收集訂閱者的處理
        typeof fn === 'function' && this.subscribes[type].push(fn);
    },
    // 發(fā)布  可能會(huì)攜帶一些信息發(fā)布出去
    publish: function() {
        var type = [].shift.call(arguments),
            fns = this.subscribes[type];
        
        // 不存在的訂閱類(lèi)型,以及訂閱時(shí)未傳入處理回調(diào)的
        if (!fns || !fns.length) {
            return;
        }
        
        // 挨個(gè)處理調(diào)用
        for (var i = 0; i < fns.length; ++i) {
            fns[i].apply(this, arguments);
        }
    },
    
    // 刪除訂閱
    remove: function(type, fn) {
        // 刪除全部
        if (typeof type === 'undefined') {
            this.subscribes = [];
            return;
        }
        var fns = this.subscribes[type];
        // 不存在的訂閱類(lèi)型,以及訂閱時(shí)未傳入處理回調(diào)的
        if (!fns || !fns.length) {
            return;
        }
        if (typeof fn === 'undefined') {
            fns.length = 0;
            return;
        }
        // 挨個(gè)處理刪除
        for (var i = 0; i < fns.length; ++i) {
            if (fns[i] === fn) {
                fns.splice(i, 1);
            }
        }
    }
};
// 訂閱崗位列表
function jobListForA(jobs) {
    console.log('A', jobs);
}
function jobListForB(jobs) {
    console.log('B', jobs);
}
// A訂閱了筆試成績(jī)
observer.subscribe('job', jobListForA);
// B訂閱了筆試成績(jī)
observer.subscribe('job', jobListForB);
// A訂閱了筆試成績(jī)
observer.subscribe('examinationA', function(score) {
    console.log(score);
});
// B訂閱了筆試成績(jī)
observer.subscribe('examinationB', function(score) {
    console.log(score);
});
// A訂閱了面試結(jié)果
observer.subscribe('interviewA', function(result) {
    console.log(result);
});
observer.publish('examinationA', 100); // 100
observer.publish('examinationB', 80); // 80
observer.publish('interviewA', '備用'); // 備用
observer.publish('job', ['前端', '后端', '測(cè)試']); // 輸出A和B的崗位
// B取消訂閱了筆試成績(jī)
observer.remove('examinationB');
// A都取消訂閱了崗位
observer.remove('job', jobListForA);
observer.publish('examinationB', 80); // 沒(méi)有可匹配的訂閱,無(wú)輸出
observer.publish('job', ['前端', '后端', '測(cè)試']); // 輸出B的崗位

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

優(yōu)點(diǎn)

一為時(shí)間上的解耦,二為對(duì)象之間的解耦??梢杂迷诋惒骄幊讨信cMV*框架中

缺點(diǎn)

創(chuàng)建訂閱者本身要消耗一定的時(shí)間和內(nèi)存,訂閱的處理函數(shù)不一定會(huì)被執(zhí)行,駐留內(nèi)存有性能開(kāi)銷(xiāo)

弱化了對(duì)象之間的聯(lián)系,復(fù)雜的情況下可能會(huì)導(dǎo)致程序難以跟蹤維護(hù)和理解

六、命令模式

1. 定義

用一種松耦合的方式來(lái)設(shè)計(jì)程序,使得請(qǐng)求發(fā)送者和請(qǐng)求接收者能夠消除彼此之間的耦合關(guān)系

命令(command)指的是一個(gè)執(zhí)行某些特定事情的指令

2. 核心

命令中帶有execute執(zhí)行、undo撤銷(xiāo)、redo重做等相關(guān)命令方法,建議顯示地指示這些方法名

3. 實(shí)現(xiàn)

簡(jiǎn)單的命令模式實(shí)現(xiàn)可以直接使用對(duì)象字面量的形式定義一個(gè)命令

var incrementCommand = {
    execute: function() {
        // something
    }
};

不過(guò)接下來(lái)的例子是一個(gè)自增命令,提供執(zhí)行、撤銷(xiāo)、重做功能

采用對(duì)象創(chuàng)建處理的方式,定義這個(gè)自增

// 自增
function IncrementCommand() {
    // 當(dāng)前值
    this.val = 0;
    // 命令棧
    this.stack = [];
    // 棧指針位置
    this.stackPosition = -1;
};
IncrementCommand.prototype = {
    constructor: IncrementCommand,
    // 執(zhí)行
    execute: function() {
        this._clearRedo();
        
        // 定義執(zhí)行的處理
        var command = function() {
            this.val += 2;
        }.bind(this);
        
        // 執(zhí)行并緩存起來(lái)
        command();
        
        this.stack.push(command);
        this.stackPosition++;
        this.getValue();
    },
    
    canUndo: function() {
        return this.stackPosition >= 0;
    },
    
    canRedo: function() {
        return this.stackPosition < this.stack.length - 1;
    },
    // 撤銷(xiāo)
    undo: function() {
        if (!this.canUndo()) {
            return;
        }
        
        this.stackPosition--;
        // 命令的撤銷(xiāo),與執(zhí)行的處理相反
        var command = function() {
            this.val -= 2;
        }.bind(this);
        
        // 撤銷(xiāo)后不需要緩存
        command();
        this.getValue();
    },
    
    // 重做
    redo: function() {
        if (!this.canRedo()) {
            return;
        }
        
        // 執(zhí)行棧頂?shù)拿?
        this.stack[++this.stackPosition]();
        this.getValue();
    },
    
    // 在執(zhí)行時(shí),已經(jīng)撤銷(xiāo)的部分不能再重做
    _clearRedo: function() {
        this.stack = this.stack.slice(0, this.stackPosition + 1);
    },
    
    // 獲取當(dāng)前值
    getValue: function() {
        console.log(this.val);
    }
};

再實(shí)例化進(jìn)行測(cè)試,模擬執(zhí)行、撤銷(xiāo)、重做的操作

var incrementCommand = new IncrementCommand();
// 模擬事件觸發(fā),執(zhí)行命令
var eventTrigger = {
    // 某個(gè)事件的處理中,直接調(diào)用命令的處理方法
    increment: function() {
        incrementCommand.execute();
    },
    incrementUndo: function() {
        incrementCommand.undo();
    },
    incrementRedo: function() {
        incrementCommand.redo();
    }
};
eventTrigger['increment'](); // 2
eventTrigger['increment'](); // 4
eventTrigger['incrementUndo'](); // 2
eventTrigger['increment'](); // 4
eventTrigger['incrementUndo'](); // 2
eventTrigger['incrementUndo'](); // 0
eventTrigger['incrementUndo'](); // 無(wú)輸出
eventTrigger['incrementRedo'](); // 2
eventTrigger['incrementRedo'](); // 4
eventTrigger['incrementRedo'](); // 無(wú)輸出
eventTrigger['increment'](); // 6

此外,還可以實(shí)現(xiàn)簡(jiǎn)單的宏命令(一系列命令的集合)

var MacroCommand = {
    commands: [],
    add: function(command) {
        this.commands.push(command);
        return this;
    },
    remove: function(command) {
        if (!command) {
            this.commands = [];
            return;
        }
        for (var i = 0; i < this.commands.length; ++i) {
            if (this.commands[i] === command) {
                this.commands.splice(i, 1);
            }
        }
    },
    execute: function() {
        for (var i = 0; i < this.commands.length; ++i) {
            this.commands[i].execute();
        }
    }
};
var showTime = {
    execute: function() {
        console.log('time');
    }
};
var showName = {
    execute: function() {
        console.log('name');
    }
};
var showAge = {
    execute: function() {
        console.log('age');
    }
};
MacroCommand.add(showTime).add(showName).add(showAge);
MacroCommand.remove(showName);
MacroCommand.execute(); // time age

七、組合模式

1. 定義

是用小的子對(duì)象來(lái)構(gòu)建更大的 對(duì)象,而這些小的子對(duì)象本身也許是由更小 的“孫對(duì)象”構(gòu)成的。

2. 核心

可以用樹(shù)形結(jié)構(gòu)來(lái)表示這種“部分- 整體”的層次結(jié)構(gòu)。

調(diào)用組合對(duì)象 的execute方法,程序會(huì)遞歸調(diào)用組合對(duì)象 下面的葉對(duì)象的execute方法

但要注意的是,組合模式不是父子關(guān)系,它是一種HAS-A(聚合)的關(guān)系,將請(qǐng)求委托給 它所包含的所有葉對(duì)象?;谶@種委托,就需要保證組合對(duì)象和葉對(duì)象擁有相同的 接口

此外,也要保證用一致的方式對(duì)待 列表中的每個(gè)葉對(duì)象,即葉對(duì)象屬于同一類(lèi),不需要過(guò)多特殊的額外操作

3. 實(shí)現(xiàn)

使用組合模式來(lái)實(shí)現(xiàn)掃描文件夾中的文件

// 文件夾 組合對(duì)象
function Folder(name) {
    this.name = name;
    this.parent = null;
    this.files = [];
}
Folder.prototype = {
    constructor: Folder,
    add: function(file) {
        file.parent = this;
        this.files.push(file);
        return this;
    },
    scan: function() {
        // 委托給葉對(duì)象處理
        for (var i = 0; i < this.files.length; ++i) {
            this.files[i].scan();
        }
    },
    remove: function(file) {
        if (typeof file === 'undefined') {
            this.files = [];
            return;
        }
        for (var i = 0; i < this.files.length; ++i) {
            if (this.files[i] === file) {
                this.files.splice(i, 1);
            }
        }
    }
};
// 文件 葉對(duì)象
function File(name) {
    this.name = name;
    this.parent = null;
}
File.prototype = {
    constructor: File,
    add: function() {
        console.log('文件里面不能添加文件');
    },
    scan: function() {
        var name = [this.name];
        var parent = this.parent;
        while (parent) {
            name.unshift(parent.name);
            parent = parent.parent;
        }
        console.log(name.join(' / '));
    }
};

構(gòu)造好組合對(duì)象與葉對(duì)象的關(guān)系后,實(shí)例化,在組合對(duì)象中插入組合或葉對(duì)象

var web = new Folder('Web');
var fe = new Folder('前端');
var css = new Folder('CSS');
var js = new Folder('js');
var rd = new Folder('后端');
web.add(fe).add(rd);
var file1 = new File('HTML權(quán)威指南.pdf');
var file2 = new File('CSS權(quán)威指南.pdf');
var file3 = new File('JavaScript權(quán)威指南.pdf');
var file4 = new File('MySQL基礎(chǔ).pdf');
var file5 = new File('Web安全.pdf');
var file6 = new File('Linux菜鳥(niǎo).pdf');
css.add(file2);
fe.add(file1).add(file3).add(css).add(js);
rd.add(file4).add(file5);
web.add(file6);
rd.remove(file4);
// 掃描
web.scan();

掃描結(jié)果為

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

優(yōu)點(diǎn)

可 以方便地構(gòu)造一棵樹(shù)來(lái)表示對(duì)象的部分-整體 結(jié)構(gòu)。在樹(shù)的構(gòu)造最終 完成之后,只需要通過(guò)請(qǐng)求樹(shù)的最頂層對(duì) 象,便能對(duì)整棵樹(shù)做統(tǒng)一一致的操作。

缺點(diǎn)

創(chuàng)建出來(lái)的對(duì)象長(zhǎng)得都差不多,可能會(huì)使代碼不好理解,創(chuàng)建太多的對(duì)象對(duì)性能也會(huì)有一些影響

八、模板方法模式

1. 定義

模板方法模式由兩部分結(jié)構(gòu)組成,第一部分是抽象父類(lèi),第二部分是具體的實(shí)現(xiàn)子類(lèi)。

2. 核心

在抽象父類(lèi)中封裝子類(lèi)的算法框架,它的 init方法可作為一個(gè)算法的模板,指導(dǎo)子類(lèi)以何種順序去執(zhí)行哪些方法。

由父類(lèi)分離出公共部分,要求子類(lèi)重寫(xiě)某些父類(lèi)的(易變化的)抽象方法

3. 實(shí)現(xiàn)

模板方法模式一般的實(shí)現(xiàn)方式為繼承

以運(yùn)動(dòng)作為例子,運(yùn)動(dòng)有比較通用的一些處理,這部分可以抽離開(kāi)來(lái),在父類(lèi)中實(shí)現(xiàn)。具體某項(xiàng)運(yùn)動(dòng)的特殊性則有自類(lèi)來(lái)重寫(xiě)實(shí)現(xiàn)。

最終子類(lèi)直接調(diào)用父類(lèi)的模板函數(shù)來(lái)執(zhí)行

// 體育運(yùn)動(dòng)
function Sport() {
}
Sport.prototype = {
    constructor: Sport,
    
    // 模板,按順序執(zhí)行
    init: function() {
        this.stretch();
        this.jog();
        this.deepBreath();
        this.start();
        var free = this.end();
        
        // 運(yùn)動(dòng)后還有空的話,就拉伸一下
        if (free !== false) {
            this.stretch();
        }
        
    },
    
    // 拉伸
    stretch: function() {
        console.log('拉伸');
    },
    
    // 慢跑
    jog: function() {
        console.log('慢跑');
    },
    
    // 深呼吸
    deepBreath: function() {
        console.log('深呼吸');
    },
    // 開(kāi)始運(yùn)動(dòng)
    start: function() {
        throw new Error('子類(lèi)必須重寫(xiě)此方法');
    },
    // 結(jié)束運(yùn)動(dòng)
    end: function() {
        console.log('運(yùn)動(dòng)結(jié)束');
    }
};
// 籃球
function Basketball() {
}
Basketball.prototype = new Sport();
// 重寫(xiě)相關(guān)的方法
Basketball.prototype.start = function() {
    console.log('先投上幾個(gè)三分');
};
Basketball.prototype.end = function() {
    console.log('運(yùn)動(dòng)結(jié)束了,有事先走一步');
    return false;
};
// 馬拉松
function Marathon() {
}
Marathon.prototype = new Sport();
var basketball = new Basketball();
var marathon = new Marathon();
// 子類(lèi)調(diào)用,最終會(huì)按照父類(lèi)定義的順序執(zhí)行
basketball.init();
marathon.init();

九、享元模式

1. 定義

享元(flyweight)模式是一種用于性能優(yōu)化的模式,它的目標(biāo)是盡量減少共享對(duì)象的數(shù)量

2. 核心

運(yùn)用共享技術(shù)來(lái)有效支持大量細(xì)粒度的對(duì)象。

強(qiáng)調(diào)將對(duì)象的屬性劃分為內(nèi)部狀態(tài)(屬性)與外部狀態(tài)(屬性)。內(nèi)部狀態(tài)用于對(duì)象的共享,通常不變;而外部狀態(tài)則剝離開(kāi)來(lái),由具體的場(chǎng)景決定。

3. 實(shí)現(xiàn)

在程序中使用了大量的相似對(duì)象時(shí),可以利用享元模式來(lái)優(yōu)化,減少對(duì)象的數(shù)量

舉個(gè)栗子,要對(duì)某個(gè)班進(jìn)行身體素質(zhì)測(cè)量,僅測(cè)量身高體重來(lái)評(píng)判

// 健康測(cè)量
function Fitness(name, sex, age, height, weight) {
    this.name = name;
    this.sex = sex;
    this.age = age;
    this.height = height;
    this.weight = weight;
}
// 開(kāi)始評(píng)判
Fitness.prototype.judge = function() {
    var ret = this.name + ': ';
    if (this.sex === 'male') {
        ret += this.judgeMale();
    } else {
        ret += this.judgeFemale();
    }
    console.log(ret);
};
// 男性評(píng)判規(guī)則
Fitness.prototype.judgeMale = function() {
    var ratio = this.height / this.weight;
    return this.age > 20 ? (ratio > 3.5) : (ratio > 2.8);
};
// 女性評(píng)判規(guī)則
Fitness.prototype.judgeFemale = function() {
    var ratio = this.height / this.weight;
    
    return this.age > 20 ? (ratio > 4) : (ratio > 3);
};
var a = new Fitness('A', 'male', 18, 160, 80);
var b = new Fitness('B', 'male', 21, 180, 70);
var c = new Fitness('C', 'female', 28, 160, 80);
var d = new Fitness('D', 'male', 18, 170, 60);
var e = new Fitness('E', 'female', 18, 160, 40);
// 開(kāi)始評(píng)判
a.judge(); // A: false
b.judge(); // B: false
c.judge(); // C: false
d.judge(); // D: true
e.judge(); // E: true

評(píng)判五個(gè)人就需要?jiǎng)?chuàng)建五個(gè)對(duì)象,一個(gè)班就幾十個(gè)對(duì)象

可以將對(duì)象的公共部分(內(nèi)部狀態(tài))抽離出來(lái),與外部狀態(tài)獨(dú)立。將性別看做內(nèi)部狀態(tài)即可,其他屬性都屬于外部狀態(tài)。

這么一來(lái)我們只需要維護(hù)男和女兩個(gè)對(duì)象(使用factory對(duì)象),而其他變化的部分則在外部維護(hù)(使用manager對(duì)象)

// 健康測(cè)量
function Fitness(sex) {
    this.sex = sex;
}
// 工廠,創(chuàng)建可共享的對(duì)象
var FitnessFactory = {
    objs: [],
    create: function(sex) {
        if (!this.objs[sex]) {
            this.objs[sex] = new Fitness(sex);
        }
        return this.objs[sex];
    }
};
// 管理器,管理非共享的部分
var FitnessManager = {
    fitnessData: {},
    
    // 添加一項(xiàng)
    add: function(name, sex, age, height, weight) {
        var fitness = FitnessFactory.create(sex);
        
        // 存儲(chǔ)變化的數(shù)據(jù)
        this.fitnessData[name] = {
            age: age,
            height: height,
            weight: weight
        };
        return fitness;
    },
    
    // 從存儲(chǔ)的數(shù)據(jù)中獲取,更新至當(dāng)前正在使用的對(duì)象
    updateFitnessData: function(name, obj) {
        var fitnessData = this.fitnessData[name];
        for (var item in fitnessData) {
            if (fitnessData.hasOwnProperty(item)) {
                obj[item] = fitnessData[item];
            }
        }
    }
};
// 開(kāi)始評(píng)判
Fitness.prototype.judge = function(name) {
    // 操作前先更新當(dāng)前狀態(tài)(從外部狀態(tài)管理器中獲?。?
    FitnessManager.updateFitnessData(name, this);
    var ret = name + ': ';
    if (this.sex === 'male') {
        ret += this.judgeMale();
    } else {
        ret += this.judgeFemale();
    }
    console.log(ret);
};
// 男性評(píng)判規(guī)則
Fitness.prototype.judgeMale = function() {
    var ratio = this.height / this.weight;
    return this.age > 20 ? (ratio > 3.5) : (ratio > 2.8);
};
// 女性評(píng)判規(guī)則
Fitness.prototype.judgeFemale = function() {
    var ratio = this.height / this.weight;
    
    return this.age > 20 ? (ratio > 4) : (ratio > 3);
};
var a = FitnessManager.add('A', 'male', 18, 160, 80);
var b = FitnessManager.add('B', 'male', 21, 180, 70);
var c = FitnessManager.add('C', 'female', 28, 160, 80);
var d = FitnessManager.add('D', 'male', 18, 170, 60);
var e = FitnessManager.add('E', 'female', 18, 160, 40);
// 開(kāi)始評(píng)判
a.judge('A'); // A: false
b.judge('B'); // B: false
c.judge('C'); // C: false
d.judge('D'); // D: true
e.judge('E'); // E: true

不過(guò)代碼可能更復(fù)雜了,這個(gè)例子可能還不夠充分,只是展示了享元模式如何實(shí)現(xiàn),它節(jié)省了多個(gè)相似的對(duì)象,但多了一些操作。

factory對(duì)象有點(diǎn)像單例模式,只是多了一個(gè)sex的參數(shù),如果沒(méi)有內(nèi)部狀態(tài),則沒(méi)有參數(shù)的factory對(duì)象就更接近單例模式了

十、職責(zé)鏈模式

1. 定義

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

2. 核心

請(qǐng)求發(fā)送者只需要知道鏈中的第一個(gè)節(jié)點(diǎn),弱化發(fā)送者和一組接收者之間的強(qiáng)聯(lián)系,可以便捷地在職責(zé)鏈中增加或刪除一個(gè)節(jié)點(diǎn),同樣地,指定誰(shuí)是第一個(gè)節(jié)點(diǎn)也很便捷

3. 實(shí)現(xiàn)

以展示不同類(lèi)型的變量為例,設(shè)置一條職責(zé)鏈,可以免去多重if條件分支

// 定義鏈的某一項(xiàng)
function ChainItem(fn) {
    this.fn = fn;
    this.next = null;
}
ChainItem.prototype = {
    constructor: ChainItem,
    
    // 設(shè)置下一項(xiàng)
    setNext: function(next) {
        this.next = next;
        return next;
    },
    
    // 開(kāi)始執(zhí)行
    start: function() {
        this.fn.apply(this, arguments);
    },
    
    // 轉(zhuǎn)到鏈的下一項(xiàng)執(zhí)行
    toNext: function() {
        if (this.next) {
            this.start.apply(this.next, arguments);
        } else {
            console.log('無(wú)匹配的執(zhí)行項(xiàng)目');
        }
    }
};
// 展示數(shù)字
function showNumber(num) {
    if (typeof num === 'number') {
        console.log('number', num);
    } else {
        // 轉(zhuǎn)移到下一項(xiàng)
        this.toNext(num);
    }
}
// 展示字符串
function showString(str) {
    if (typeof str === 'string') {
        console.log('string', str);
    } else {
        this.toNext(str);
    }
}
// 展示對(duì)象
function showObject(obj) {
    if (typeof obj === 'object') {
        console.log('object', obj);
    } else {
        this.toNext(obj);
    }
}
var chainNumber = new ChainItem(showNumber);
var chainString = new ChainItem(showString);
var chainObject = new ChainItem(showObject);
// 設(shè)置鏈條
chainObject.setNext(chainNumber).setNext(chainString);
chainString.start('12'); // string 12
chainNumber.start({}); // 無(wú)匹配的執(zhí)行項(xiàng)目
chainObject.start({}); // object {}
chainObject.start(123); // number 123

這時(shí)想判斷未定義的時(shí)候呢,直接加到鏈中即可

// 展示未定義
function showUndefined(obj) {
    if (typeof obj === 'undefined') {
        console.log('undefined');
    } else {
        this.toNext(obj);
    }
}
var chainUndefined = new ChainItem(showUndefined);
chainString.setNext(chainUndefined);
chainNumber.start(); // undefined

由例子可以看到,使用了職責(zé)鏈后,由原本的條件分支換成了很多對(duì)象,雖然結(jié)構(gòu)更加清晰了,但在一定程度上可能會(huì)影響到性能,所以要注意避免過(guò)長(zhǎng)的職責(zé)鏈。

十一、中介者模式

1. 定義

所有的相關(guān) 對(duì)象都通過(guò)中介者對(duì)象來(lái)通信,而不是互相引用,所以當(dāng)一個(gè)對(duì)象發(fā)生改變時(shí),只需要通知中介者對(duì)象即可

2. 核心

使網(wǎng)狀的多對(duì)多關(guān)系變成了相對(duì)簡(jiǎn)單的一對(duì)多關(guān)系(復(fù)雜的調(diào)度處理都交給中介者)

使用中介者后

3. 實(shí)現(xiàn)

多個(gè)對(duì)象,指的不一定得是實(shí)例化的對(duì)象,也可以將其理解成互為獨(dú)立的多個(gè)項(xiàng)。當(dāng)這些項(xiàng)在處理時(shí),需要知曉并通過(guò)其他項(xiàng)的數(shù)據(jù)來(lái)處理。

如果每個(gè)項(xiàng)都直接處理,程序會(huì)非常復(fù)雜,修改某個(gè)地方就得在多個(gè)項(xiàng)內(nèi)部修改

我們將這個(gè)處理過(guò)程抽離出來(lái),封裝成中介者來(lái)處理,各項(xiàng)需要處理時(shí),通知中介者即可。

var A = {
    score: 10,
    changeTo: function(score) {
        this.score = score;
        // 自己獲取
        this.getRank();
    },
    
    // 直接獲取
    getRank: function() {
        var scores = [this.score, B.score, C.score].sort(function(a, b) {
            return a < b;
        });
        console.log(scores.indexOf(this.score) + 1);
    }
};
var B = {
    score: 20,
    changeTo: function(score) {
        this.score = score;
        // 通過(guò)中介者獲取
        rankMediator(B);
    }
};
var C = {
    score: 30,
    changeTo: function(score) {
        this.score = score;
        rankMediator(C);
    }
};
// 中介者,計(jì)算排名
function rankMediator(person) {
    var scores = [A.score, B.score, C.score].sort(function(a, b) {
        return a < b;
    });
    console.log(scores.indexOf(person.score) + 1);
}
// A通過(guò)自身來(lái)處理
A.changeTo(100); // 1
// B和C交由中介者處理
B.changeTo(200); // 1
C.changeTo(50); // 3

ABC三個(gè)人分?jǐn)?shù)改變后想要知道自己的排名,在A中自己處理,而B(niǎo)和C使用了中介者。B和C將更為輕松,整體代碼也更簡(jiǎn)潔

最后,雖然中介者做到了對(duì)模塊和對(duì)象的解耦,但有時(shí)對(duì)象之間的關(guān)系并非一定要解耦,強(qiáng)行使用中介者來(lái)整合,可能會(huì)使代碼更為繁瑣,需要注意。

十二、裝飾者模式

1. 定義

以動(dòng)態(tài)地給某個(gè)對(duì)象添加一些額外的職責(zé),而不會(huì)影響從這個(gè)類(lèi)中派生的其他對(duì)象。

是一種“即用即付”的方式,能夠在不改變對(duì) 象自身的基礎(chǔ)上,在程序運(yùn)行期間給對(duì)象動(dòng)態(tài)地 添加職責(zé)

2. 核心

是為對(duì)象動(dòng)態(tài)加入行為,經(jīng)過(guò)多重包裝,可以形成一條裝飾鏈

3. 實(shí)現(xiàn)

最簡(jiǎn)單的裝飾者,就是重寫(xiě)對(duì)象的屬性

var A = {
    score: 10
};
A.score = '分?jǐn)?shù):' + A.score;

可以使用傳統(tǒng)面向?qū)ο蟮姆椒▉?lái)實(shí)現(xiàn)裝飾,添加技能

function Person() {}
Person.prototype.skill = function() {
    console.log('數(shù)學(xué)');
};
// 裝飾器,還會(huì)音樂(lè)
function MusicDecorator(person) {
    this.person = person;
}
MusicDecorator.prototype.skill = function() {
    this.person.skill();
    console.log('音樂(lè)');
};
// 裝飾器,還會(huì)跑步
function RunDecorator(person) {
    this.person = person;
}
RunDecorator.prototype.skill = function() {
    this.person.skill();
    console.log('跑步');
};
var person = new Person();
// 裝飾一下
var person1 = new MusicDecorator(person);
person1 = new RunDecorator(person1);
person.skill(); // 數(shù)學(xué)
person1.skill(); // 數(shù)學(xué) 音樂(lè) 跑步

在JS中,函數(shù)為一等對(duì)象,所以我們也可以使用更通用的裝飾函數(shù)

// 裝飾器,在當(dāng)前函數(shù)執(zhí)行前先執(zhí)行另一個(gè)函數(shù)
function decoratorBefore(fn, beforeFn) {
    return function() {
        var ret = beforeFn.apply(this, arguments);
        
        // 在前一個(gè)函數(shù)中判斷,不需要執(zhí)行當(dāng)前函數(shù)
        if (ret !== false) {
            fn.apply(this, arguments);
        }
    };
}
function skill() {
    console.log('數(shù)學(xué)');
}
function skillMusic() {
    console.log('音樂(lè)');
}
function skillRun() {
    console.log('跑步');
}
var skillDecorator = decoratorBefore(skill, skillMusic);
skillDecorator = decoratorBefore(skillDecorator, skillRun);
skillDecorator(); // 跑步 音樂(lè) 數(shù)學(xué)

十三、狀態(tài)模式

1. 定義

事物內(nèi)部狀態(tài)的改變往往會(huì)帶來(lái)事物的行為改變。在處理的時(shí)候,將這個(gè)處理委托給當(dāng)前的狀態(tài)對(duì)象即可,該狀態(tài)對(duì)象會(huì)負(fù)責(zé)渲染它自身的行為

2. 核心

區(qū)分事物內(nèi)部的狀態(tài),把事物的每種狀態(tài)都封裝成單獨(dú)的類(lèi),跟此種狀態(tài)有關(guān)的行為都被封裝在這個(gè)類(lèi)的內(nèi)部

3. 實(shí)現(xiàn)

以一個(gè)人的工作狀態(tài)作為例子,在剛醒、精神、疲倦幾個(gè)狀態(tài)中切換著

// 工作狀態(tài)
function Work(name) {
    this.name = name;
    this.currentState = null;
    // 工作狀態(tài),保存為對(duì)應(yīng)狀態(tài)對(duì)象
    this.wakeUpState = new WakeUpState(this);
    // 精神飽滿
    this.energeticState = new EnergeticState(this);
    // 疲倦
    this.tiredState = new TiredState(this);
    this.init();
}
Work.prototype.init = function() {
    this.currentState = this.wakeUpState;
    
    // 點(diǎn)擊事件,用于觸發(fā)更新?tīng)顟B(tài)
    document.body.onclick = () => {
        this.currentState.behaviour();
    };
};
// 更新工作狀態(tài)
Work.prototype.setState = function(state) {
    this.currentState = state;
}
// 剛醒
function WakeUpState(work) {
    this.work = work;
}
// 剛醒的行為
WakeUpState.prototype.behaviour = function() {
    console.log(this.work.name, ':', '剛醒呢,睡個(gè)懶覺(jué)先');
    
    // 只睡了2秒鐘懶覺(jué)就精神了..
    setTimeout(() => {
        this.work.setState(this.work.energeticState);
    }, 2 * 1000);
}
// 精神飽滿
function EnergeticState(work) {
    this.work = work;
}
EnergeticState.prototype.behaviour = function() {
    console.log(this.work.name, ':', '超級(jí)精神的');
    
    // 才精神1秒鐘就發(fā)困了
    setTimeout(() => {
        this.work.setState(this.work.tiredState);
    }, 1000);
};
// 疲倦
function TiredState(work) {
    this.work = work;
}
TiredState.prototype.behaviour = function() {
    console.log(this.work.name, ':', '怎么肥事,好困');
    
    // 不知不覺(jué),又變成了剛醒著的狀態(tài)... 不斷循環(huán)呀
    setTimeout(() => {
        this.work.setState(this.work.wakeUpState);
    }, 1000);
};
var work = new Work('曹操');

點(diǎn)擊一下頁(yè)面,觸發(fā)更新?tīng)顟B(tài)的操作

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

優(yōu)點(diǎn)

狀態(tài)切換的邏輯分布在狀態(tài)類(lèi)中,易于維護(hù)

缺點(diǎn)

多個(gè)狀態(tài)類(lèi),對(duì)于性能來(lái)說(shuō),也是一個(gè)缺點(diǎn),這個(gè)缺點(diǎn)可以使用享元模式來(lái)做進(jìn)一步優(yōu)化

將邏輯分散在狀態(tài)類(lèi)中,可能不會(huì)很輕易就能看出狀態(tài)機(jī)的變化邏輯

十四、適配器模式

1. 定義

是解決兩個(gè)軟件實(shí)體間的接口不兼容的問(wèn)題,對(duì)不兼容的部分進(jìn)行適配

2. 核心

解決兩個(gè)已有接口之間不匹配的問(wèn)題

3. 實(shí)現(xiàn)

比如一個(gè)簡(jiǎn)單的數(shù)據(jù)格式轉(zhuǎn)換的適配器

// 渲染數(shù)據(jù),格式限制為數(shù)組了
function renderData(data) {
    data.forEach(function(item) {
        console.log(item);
    });
}
// 對(duì)非數(shù)組的進(jìn)行轉(zhuǎn)換適配
function arrayAdapter(data) {
    if (typeof data !== 'object') {
        return [];
    }
    if (Object.prototype.toString.call(data) === '[object Array]') {
        return data;
    }
    var temp = [];
    for (var item in data) {
        if (data.hasOwnProperty(item)) {
            temp.push(data[item]);
        }
    }
    return temp;
}
var data = {
    0: 'A',
    1: 'B',
    2: 'C'
};
renderData(arrayAdapter(data)); // A B C

十五、外觀模式

1. 定義

為子系統(tǒng)中的一組接口提供一個(gè)一致的界面,定義一個(gè)高層接口,這個(gè)接口使子系統(tǒng)更加容易使用

2. 核心

可以通過(guò)請(qǐng)求外觀接口來(lái)達(dá)到訪問(wèn)子系統(tǒng),也可以選擇越過(guò)外觀來(lái)直接訪問(wèn)子系統(tǒng)

3. 實(shí)現(xiàn)

外觀模式在JS中,可以認(rèn)為是一組函數(shù)的集合

// 三個(gè)處理函數(shù)
function start() {
    console.log('start');
}
function doing() {
    console.log('doing');
}
function end() {
    console.log('end');
}
// 外觀函數(shù),將一些處理統(tǒng)一起來(lái),方便調(diào)用
function execute() {
    start();
    doing();
    end();
}
// 調(diào)用init開(kāi)始執(zhí)行
function init() {
    // 此處直接調(diào)用了高層函數(shù),也可以選擇越過(guò)它直接調(diào)用相關(guān)的函數(shù)
    execute();
}
init(); // start doing end

以上就是一文總結(jié)JavaScript中常見(jiàn)的設(shè)計(jì)模式的詳細(xì)內(nèi)容,更多關(guān)于JavaScript設(shè)計(jì)模式的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • JavaScript中二分查找的例題詳解

    JavaScript中二分查找的例題詳解

    二分查找在我們學(xué)習(xí)算法中是很重要的一部分,而且面試的時(shí)候會(huì)經(jīng)常的讓我們手寫(xiě)一些算法。所以這篇文章將通過(guò)三個(gè)場(chǎng)景帶大家深入了解二分查找算法
    2023-03-03
  • layui 表格的屬性的顯示轉(zhuǎn)換方法

    layui 表格的屬性的顯示轉(zhuǎn)換方法

    今天小編就為大家分享一篇layui 表格的屬性的顯示轉(zhuǎn)換方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-08-08
  • 微信js-sdk上傳與下載圖片接口用法示例

    微信js-sdk上傳與下載圖片接口用法示例

    這篇文章主要介紹了微信js-sdk上傳與下載圖片接口用法,結(jié)合實(shí)例形式分析了基于上傳圖片接口(uploadImage)和下載圖片接口(downloadImage)針對(duì)圖片操作的相關(guān)技巧,需要的朋友可以參考下
    2016-10-10
  • js實(shí)現(xiàn)圓形顯示鼠標(biāo)單擊位置

    js實(shí)現(xiàn)圓形顯示鼠標(biāo)單擊位置

    這篇文章主要為大家詳細(xì)介紹了js實(shí)現(xiàn)圓形顯示鼠標(biāo)單擊位置,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-02-02
  • Javascript中各種trim的實(shí)現(xiàn)詳細(xì)解析

    Javascript中各種trim的實(shí)現(xiàn)詳細(xì)解析

    這篇文章主要是對(duì)Javascript中各種trim的實(shí)現(xiàn)進(jìn)行了詳細(xì)的介紹,需要的朋友可以過(guò)來(lái)參考下,希望對(duì)大家有所幫助
    2013-12-12
  • javascript求日期差的方法

    javascript求日期差的方法

    這篇文章主要介紹了javascript求日期差的方法,涉及JavaScript日期及字符串操作的相關(guān)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2016-03-03
  • js 操作符實(shí)例代碼

    js 操作符實(shí)例代碼

    一個(gè)單例對(duì)象,承載大多數(shù)常用的函數(shù)列表
    2009-10-10
  • JavaScript實(shí)現(xiàn)LI列表數(shù)據(jù)綁定的方法

    JavaScript實(shí)現(xiàn)LI列表數(shù)據(jù)綁定的方法

    這篇文章主要介紹了JavaScript實(shí)現(xiàn)LI列表數(shù)據(jù)綁定的方法,可實(shí)現(xiàn)綁定Li列表項(xiàng)對(duì)應(yīng)數(shù)值項(xiàng)的功能,涉及javascript鼠標(biāo)onmousemove、onmouseout及onclick等事件的相關(guān)使用技巧,需要的朋友可以參考下
    2015-08-08
  • javascript實(shí)現(xiàn)鼠標(biāo)移到Image上方時(shí)顯示文字效果的方法

    javascript實(shí)現(xiàn)鼠標(biāo)移到Image上方時(shí)顯示文字效果的方法

    這篇文章主要介紹了javascript實(shí)現(xiàn)鼠標(biāo)移到Image上方時(shí)顯示文字效果的方法,涉及javascript鼠標(biāo)事件及圖文屬性動(dòng)態(tài)設(shè)置的相關(guān)技巧,可用于為圖片增加文字提示效果,需要的朋友可以參考下
    2015-08-08
  • JS請(qǐng)求servlet功能示例

    JS請(qǐng)求servlet功能示例

    這篇文章主要介紹了JS請(qǐng)求servlet功能,結(jié)合具體實(shí)例形式分析了javascript使用ajax請(qǐng)求servlet端響應(yīng)的主要功能代碼與相關(guān)操作技巧,需要的朋友可以參考下
    2017-06-06

最新評(píng)論