零、原鏈和資料
1.js設計模式
注: 代碼均爲簡潔版,如需瞭解其他版本寫法的優缺點,請移步原文章。
一、單體模式
其思想是在一定的作用域範圍內保證一個特定類僅有一個實例,意味着當你第二次使用同一個類創建信對象時,應得到和第一次創建對象完全相同。
var Universe; (function(){ var instance; Universe=function Universe(){ if(instance){ return instance; } instance=this; this.xx="xx"; } })(); var uni = new Universe(); Universe.prototype.a = 1 var uni2 = new Universe(); console.log(uni === uni2) //true console.log(uni.a) //1 console.log(uni2.a) //1 console.log(uni.constructor === Universe); //true
二、工廠模式
批量創建對象,可根據不同要求小範圍內調整,就如同工廠一樣,零件我可以採購,然後根據要求生產福特/mini/沃爾沃...
demo:
- 公共構造函數 CarMaker
- 名爲 factory 的 CarMaker 靜態方法來創建 car 對象
function CarMaker() {} // 原型鏈上 CarMaker.prototype.drive = () => { return `I have ${this.doors} doors`; } // 靜態方法 // 枚舉子對象 CarMaker.compact = () => { this.doors = 4; } CarMaker.convertible = () => { this.doors = 2; } CarMaker.suv = () => { this.doors = 6; } CarMaker.factory = (type) => { if (typeof CarMaker[type] !== 'function') { throw new ReferenceError('Error') } // 關鍵句,因爲 CarMaker.compact/convertible/suv 是獨立的函數,並不在 CarMaker 的繼承鏈上 // 所以這裏關聯到 CarMaker 的繼承鏈上 if (typeof CarMaker[type].prototype.drive !== 'function') { CarMaker[type].prototype = new CarMaker(); } return new CarMaker[type](); } var corolla = CarMaker.factory('compact'); console.log(corolla.drive()); //I have 4 doors
是不是和 Object.create({...})很像
三、迭代器模式
emmm... 用來迭代複雜數據結構的。
基礎的 api 設計:
- next() 下一個
- hasNext() 是否有下一個
- reWind() 重置指針
- current() 返回當前
var agg = (function() { var index = 0; var data = [1, 2, 3, 4, 5, 6]; var length = data.length; return { next: function() { //這裏是從第一個數據開始輸出 本例中爲 1 if (!this.hasNext()) { return null; } var element = data[index]; index++; return element; }, hasNext: function() { return index < length; }, reWind: function() { index = 0; }, current: function() { return data[index]; } } })(); while (agg.hasNext()) { console.log(agg.next()); //1,2,3,4,5,6 } agg.reWind(); //此時重置指針到0
四、裝飾者模式
可以在運行時候添加附加功能到對象中,其一個方便特徵在於其預期行爲的可定製和可配置特性。
demo:
假設在開發一個銷售商品的Web應用,每一筆信銷售都是一個人新的 sale 對象。該對象“知道”有關項目的價格,並可以通過 getPrice() 方法返回加個。
根據不同情況,可以用額外的功能裝飾此對象。
假設客戶在魁北克省,買房需要支付聯邦稅和魁北克省稅,則此時需要調用聯邦稅裝飾者和魁北克省稅裝飾者。
// 實例及要求 var sale=new Sale(100); sale=sale.decorate("fedtax"); //聯邦稅 sale=sale.decorate("quebec"); //魁北克省稅 sale=sale.decorate("miney"); //轉爲美元格式 sale.getPrice(); //返回價格
function Sale(price) { this.price = price; this.decorateList = []; } // 注意這裏是靜態方法 Sale.decorators = {}; Sale.decorators.fedtax = { getPrice: function(price) { var price = this.uber.getPrice(); return price * 0.8; //對price進行處理 }, } Sale.decorators.quebec = { getPrice: function(price) { var price = this.uber.getPrice(); return price * 0.7; //對price進行處理 }, } Sale.decorators.money = { getPrice: function(price) { var price = this.uber.getPrice(); return "$" + price * 0.9; //對price進行處理 }, } Sale.prototype.decorate = function(decorator) { this.decorateList.push(decorator); return this; }; Sale.prototype.getPrice = function() { var price = this.price; this.decorateList.forEach(function(name) { price = Sale.decorators[name].getPrice(price); }); return price; }; var sale = new Sale(100); sale = sale.decorate("fedtax"); //聯邦稅 sale = sale.decorate("quebec"); //魁北克省稅 sale = sale.decorate("money"); //轉爲美元格式 console.log(sale.getPrice()); //$50.4
便於鏈式調用的改版:
function Sale(price) { this.price = price; this.decorateList = []; } Sale.decorators = {}; // 自定義的裝飾者 Sale.decorators.fedTax = { getPrice(price) { return price * 0.8; } } Sale.decorators.queBec = { getPrice(price) { return price * 0.7; } } Sale.decorators.money = { getPrice: function(price) { return "$" + price * 0.9; //對price進行處理 }, } Sale.prototype.decorate = function(decorator) { this.decorateList.push(decorator); return this; // 鏈式調用 } Sale.prototype.getPrice = function() { let price = this.price; this.decorateList.forEach(name => { price = Sale.decorators[name].getPrice(price); }) return price; } let sale = new Sale(100); sale.decorate('fedTax').decorate('queBec').decorate('money'); console.log(sale.getPrice()); //$50.4
五、策略模式
策略模式支持在運行時候選擇算法。
例如用在表單驗證問題上,可以創建一個具有 validate() 方法的驗證器對象,無論表單具體類型是什麼,該方法都會被調用,並且返回結果或者錯誤信息。 其核心思想是:預先定義各種規則字典(types), 然後定義數據(data)與規則字典的對應關係(config), 接着循環數據的每個字段,執行匹配的規則檢驗,蒐集對應的 success/error 信息。
// 定義驗證器對象 let validator = { // 所有可以的驗證規則處理類存放的地方,後面會單獨定義 types: {}, // 驗證類型所對應的錯誤信息 messages: [], // 當然需要使用的驗證類型 config: {}, // 暴露的公開驗證方法 // 傳入的參數是 key: value 對 validate: function(data) { let i, msg, type, checker, result_ok; // 清空所有的錯誤信息 this.messages = []; for (i in data) { if (data.hasOwnProperty(i)) { type = this.config[i]; // 根據key查詢是否有存在的驗證規則 checker = this.types[type]; // 獲取驗證規則的驗證類 if (!type) continue; // 如果驗證規則不存在,則不處理 if (!checker) { // 如果驗證規則類不存在,拋出異常 throw { name: 'ValidationError', message: 'No handler to validate type ' + type }; } result_ok = checker.validate(data[i]); // 使用查到的單個驗證類進行驗證 if (!result_ok) { msg = "Invalid value for *" + i + "*, " + checker.instructions; this.messages.push(msg); } } } return this.hasErrors(); }, hasErrors: function() { return this.messages.length !== 0; } } // 然後剩下的工作,就是定義types裏存放的各種驗證類了 // 驗證給定的值是否不爲空 validator.types.isNonEmpty = { validate: function(value) { return value !== ''; }, instructions: '傳入的值不能爲空', }; // 驗證給定的值是否是數字 validator.types.isNumber = { validate: function(value) { return !isNaN(value); }, instructions: '傳入的值只能是合法的數字,例如:1, 3.14 or 2010', }; // 驗證給定的值是否只是字母或數字 validator.types.isAlphaNum = { validate: function(value) { return !/[^a-z0-9]/i.test(value); }, instructions: '傳入的值只能保護字母和數字,不能包含特殊字符', } // 使用的時候,我們首先要定義需要驗證的數據集合,然後還需要定義每種數據需要驗證的規則類型,代碼如下: let data = { first_name: 'Tom', last_name: 'Xu', age: 'unknown', username: 'TomXu@', } // 配置下驗證規則 validator.config = { first_name: 'isNonEmpty', age: 'isNumber', username: 'isAlphaNum' } // 最後獲取驗證結果 validator.validate(data); if (validator.hasErrors()) { console.log(validator.messages.join('\n')); }
策略模式可以作爲 switch-case 的拓展版本。
六、代理模式
在代理模式中,一個對象充當另外一個對象的接口。
代理模式是介於對象的客戶端和對象本身之間,並且對該對象的訪問進行保護。
原理是:在 代理對象中實例化原來需要的對象,然後調用相應的方法。
注: 在 es6 中有一個原生的代理對象:Proxy,vue 3.0 也將基於這個代理對象。
demo: 賣家 - 郵局(proxy) - 買家
let Package = function(receiver) { this.receiver = receiver; } let Seller = function(good) { this.package = good; this.send = function(gift) { return good.receiver + '你的包裹' + gift; } } let Express = function(good) { this.package = good; this.send = function(packageName) { return new Seller(good).send(packageName); } } let ems = new Express(new Package('gary')); console.log(ems.send('鍵盤'));
七、中介者模式
中介者模式可以讓多個對象之間鬆耦合,並降低維護成本。
在中介者模式下,對象與對象之間的聯繫全靠中介者,者意味着除中介者外,其他對象不知道還有沒有另外的的對象,這是中介者模式與代理模式不一樣的地方。
function Player(name) { this.point = 0; this.name = name; } Player.prototype.play = function() { this.point += 1; mediator.played(); // mediator 中介 } let scoreBoard = { element: document.querySelector('#score'), // '這裏放 dom 元素,用來顯示分數' update: function(score) { // 更新分數 let msg = ''; for (let i in score) { if (score.hasOwnProperty(i)) { msg += score[i] + '-'; } } this.element.innerText = msg; }, } let mediator = { // 中介 players: {}, // 存放玩家對象 setUp: function() { let players = this.players; players.home = new Player('home'); players.guest = new Player('guest'); }, played() { let players = this.players; let score = { home: players.home.point, guest: players.guest.point }; scoreBoard.update(score); }, keypress: function(e) { e = e || window.event; if (e.which === 49) { // 按鍵 1 return mediator.players.home.play(); } if (e.which === 48) { // 按鍵 0 return mediator.players.guest.play(); } }, } mediator.setUp(); // 啓動(初始化) window.onkeypress = mediator.keypress; setTimeout(function() { //設置5秒遊戲時間 window.onkeypress = null; alert("game end"); }, 5000);
八、觀察者/訂閱-發佈模式
詳情請移步 js - 觀察者模式與訂閱發佈模式