《JavaScript設計模式與開發實踐》14種設計模式學習筆記。算法
保證一個類僅有一個實例,並提供一個全局訪問點。設計模式
// 構造函數 var Singleton = function (name) { this.name = name; } Singleton.prototype.getName = function () { alert(this.name); } // 方案1 綁定到構造函數上 Singleton.instance = null; Singleton.getInstance = function (name) { if (!this.instance) { this.instance = new Singleton(name); } return this.instance; } // 方案2 閉包 Singleton.getInstance = (function () { var instance = null; return function (name) { if (!instance) { instance = new Singleton(name); } return instance; } })()
須要的時候才建立對象實例緩存
var getSingle = function (fn) { var result; return function () { return result || (result = fn.apply(this, arguments)); } }
例如一個Modal彈框,頁面加載時不須要建立,按鈕點擊後纔會被建立。之後再點擊按鈕不須要建立新的Modal。性能優化
var createModal = function () { var div = document.createElement('div'); div.style.display = 'none'; document.body.append(div); return div; } createSingleModal = getSingle(createModal); btn.click = function () { var modal = createSingleModal(); modal.style.display = 'block'; }
定義一系列算法,把它們一個個封裝起來,而且使它們能夠互相替換。在實際開發中,咱們一般會把算法的含義擴散開來,使用策略模式也能夠用來封裝一系列目標一致的‘業務規則’。閉包
例如計算年終獎:app
var strategies = { "S": function (salary) { return salary * 4; }, "A": function (salary) { return salary * 3; }, "B": function (salary) { return salary * 2; }, } var calculateBonus = function (level, salary) { return strategies[level](salary); } calculateBonus('S', 20000); // 80000 calculateBonus('A', 10000); // 30000
顧名思義,代理。框架
當直接訪問本體不方便或者不符合須要時,爲這個本體提供一個替代者。dom
虛擬代理吧一些開銷很大的對象,延遲到真正須要他的時候纔去建立。函數
虛擬代理實現圖片預加載性能
var myImage = (function () { var imgNode = document.createElement('img'); document.body.append(imgNode); return function (src) { imgNode.src = src; } })() // 代理 myImage,實現預加載 var proxyImage = (function () { var img = new Image(); img.onload = function () { myImage(this.src); } return function (src) { myImage('./loading.gif'); img.src = src; } })() proxyImage('http://xxx.10M.png');
JavaScript 開發中最經常使用的是虛擬代理和緩存代理。
迭代器模式是指提供一種方法順序訪問一個聚合對象中的各個元素,而又不須要暴露該對象的內部表示。
實現迭代器(內部迭代器)
var each = function (arr, callback) { for (var i = 0; i < arr.length; i++) { if (callback.call(arr[i], arr[i], i) === false) { break; } } } each([1, 2, 3, 4, 5], function (item, index) { if (index > 3) { return false; } console.log(item, index); })
外部迭代器
var Iterator = function (obj) { var current = 0; var next = function () { current += 1; }; var isDone = function () { return current >= obj.length; }; var getCurrItem = function () { return obj[current]; }; return { next: next, isDone: isDone, getCurrItem: getCurrItem, length: obj.length, } }
定義對象間的一種一對多的依賴關係,當一個對象的狀態發生改變時,全部依賴於它的對象都將獲得通知。
var event = { clientList: {}, listen: function (key, fn) { if (!clientList[key]) { clientList[key] = []; } clientList[key].push(fn); }, trigger: function () { var key = Array.prototype.shift.apply(arguments), fns = this.clientList[key]; if (!fns || fns.length === 0) { return false; } for (var i = 0; i < fns.length; i++) { fns[i].apply(this, arguments); } }, remove: function (key, fn) { var fns = this.clientList[key]; if (!fns || fns.length === 0) { return false; } if (!fn) { this.clientList = []; } else { for (var i = 0; i < fns.length; i++) { if (fn === fns[i]) { fns.splice(i, 1); } } } }, }
DOM事件也是發佈-訂閱模式。
命令模式把代碼封裝成命令,目的解藕。
命令模式有 接收者receiver,執行方法execute;execute 去執行 receiver.xxx()。
var setCommand = function (button, command) { button.onClick = function () { command.execute(); } } var MenuBar = { refresh: function () { console.log('刷新菜單') } } var RefreshMenuBarCommand = function (receiver) { this.receiver = receiver; } RefreshMenuBarCommand.prototype.execute = function () { this.receiver.refresh(); } var refreshMenuBarCommand = new RefreshMenuBarCommand(MenuBar); setCommand(btn, refreshMenuBarCommand);
JavaScript 中的命令模式未必要使用面向對象:
var setCommand = function (button, command) { button.onClick = function () { command.execute(); } } var MenuBar = { refresh: function () { console.log('刷新菜單'); } } var RefreshMenuBarCommand = function (receiver) { return { execute: function () { receiver.refresh(); } } } var refreshMenuBarCommand = RefreshMenuBarCommand(MenuBar); setCommand(btn, refreshMenuBarCommand);
JavaScript 中命令模式還能夠不須要接收者:
var refreshMenuBarCommand = { execute: function () { console.log('刷新菜單'); } };
這樣看起來代碼結構和策略模式很是相近,但他們的意圖不一樣,策略模式的策略對象的目標老是一致的,命令模式的目標更具散發性,命令模式還能夠完成撤銷,排隊等功能。
組合模式中有兩個名詞:組合對象,葉對象。
結構如圖
組合模式的例子-掃描文件夾
// Folder var Folder = function (name) { this.name = name; this.parent = null; this.files = []; }; Folder.prototype.add = function (file) { file.parent = this; this.files.push(file); }; Folder.prototype.remove = function () { if (!this.parent) { return; } for (var files = this.parent.files, i = 0; i < files.length; i++) { var file = files[i]; if (file === this) { files.splice(i, 1); break; } } }; Folder.prototype.scan = function () { console.log('開始掃描文件夾:' + this.name); for (var i = 0; i < this.files.length; i++) { var file = this.files[i]; file.scan(); } }; // File var File = function (name) { this.name = name; this.parent = null; }; File.prototype.add = function () { throw new Error('文件夾下面不能在添加文件'); }; Folder.prototype.remove = function () { if (!this.parent) { return; } for (var files = this.parent.files, i = 0; i < files.length; i++) { var file = files[i]; if (file === this) { files.splice(i, 1); break; } } }; File.prototype.scan = function () { console.log('開始掃描文件:' + this.name); }; // 測試 var folder = new Folder('學習資料'); var folder1 = new Folder('JavaScript'); folder1.add(new File('JavaScript設計模式與開發實踐')); folder.add(folder1); folder.add(new File('深刻淺出Node.js')); console.log('第一次掃描'); folder.scan(); folder1.remove(); console.log('第二次掃描'); folder.scan();
模板方法模式是一種只需使用繼承就能夠實現的很是簡單的模式。
模板方法模式由兩部分結構組成,第一部分是抽象父類,第二部分是具體的實現子類。
假如咱們有一些平行的子類,各個子類之間有一些相同的行爲,也有一些不一樣的行爲。若是 相同和不一樣的行爲都混合在各個子類的實現中,說明這些相同的行爲會在各個子類中重複出現。 但實際上,相同的行爲能夠被搬移到另一個單一的地方,模板方法模式就是爲解決這個問題而 生的。在模板方法模式中,子類實現中的相同部分被上移到父類中,而將不一樣的部分留待子類來 實現。這也很好地體現了泛化的思想。
例子:咖啡與茶
先泡一杯咖啡
var Coffee = function () { }; Coffee.prototype.boilWater = function () { console.log('把水煮沸'); }; Coffee.prototype.brewCoffeeGriends = function () { console.log('用沸水沖泡咖啡'); }; Coffee.prototype.pourInCup = function () { console.log('把咖啡倒進杯子'); }; Coffee.prototype.addSugarAndMilk = function () { console.log('加糖和牛奶'); }; Coffee.prototype.init = function () { this.boilWater(); this.brewCoffeeGriends(); this.pourInCup(); this.addSugarAndMilk(); }; var coffee = new Coffee(); coffee.init();
泡一壺茶
var Tea = function () { }; Tea.prototype.boilWater = function () { console.log('把水煮沸'); }; Tea.prototype.steepTeaBag = function () { console.log('用沸水浸泡茶葉'); }; Tea.prototype.pourInCup = function () { console.log('把茶水倒進杯子'); }; Tea.prototype.addLemon = function () { console.log('加檸檬'); }; Tea.prototype.init = function () { this.boilWater(); this.steepTeaBag(); this.pourInCup(); this.addLemon(); }; var tea = new Tea(); tea.init();
分離出共同點
var Beverage = function () { }; Beverage.prototype.boilWater = function () { console.log('把水煮沸'); }; Beverage.prototype.brew = function () { }; // 空方法,應該由子類重寫 Beverage.prototype.pourInCup = function () { }; // 空方法,應該由子類重寫 Beverage.prototype.addCondiments = function () { }; // 空方法,應該由子類重寫 Beverage.prototype.init = function () { this.boilWater(); this.brew(); this.pourInCup(); this.addCondiments(); };
建立Coffee子類
var Coffee = function () { }; Coffee.prototype = new Beverage(); Coffee.prototype.brew = function () { console.log('用沸水沖泡咖啡'); }; Coffee.prototype.pourInCup = function () { console.log('把咖啡倒進杯子'); }; Coffee.prototype.addCondiments = function () { console.log('加糖和牛奶'); }; var coffee = new Coffee(); coffee.init();
建立Tea子類
var Tea = function () { }; Tea.prototype = new Beverage(); Tea.prototype.brew = function () { console.log('用沸水浸泡茶葉'); }; Tea.prototype.pourInCup = function () { console.log('把茶倒進杯子'); }; Tea.prototype.addCondiments = function () { console.log('加檸檬'); }; var tea = new Tea(); tea.init();
Beverage.prototype.init 被稱爲模板方法的緣由是,該方法中封裝了子類的算法框架,它做 爲一個算法的模板,指導子類以何種順序去執行哪些方法。在 Beverage.prototype.init 方法中, 算法內的每個步驟都清楚地展現在咱們眼前。
享元模式是一種用於性能優化的模式,核心是運用共享技術來有效支持大量細粒度的對象。
內部狀態 儲存於共享對象內部,而 外部狀態 儲存於共享對象的外部,在必要時被傳入共享對象來組裝成一個完整的對象。
上傳文件的例子
下面代碼同時選擇 2000 個文件時,會 new 2000 個 upload 對象。
var id = 0; window.startUpload = function (uploadType, files) { // uploadType 區分是控件仍是 flash for (var i = 0; i < files.length; i++) { var file = files[i]; var uploadObj = new Upload(uploadType, file.fileName, file.fileSize); uploadObj.init(id++); } }; var Upload = function (uploadType, fileName, fileSize) { this.uploadType = uploadType; this.fileName = fileName; this.fileSize = fileSize; this.dom = null; }; Upload.prototype.init = function (id) { var that = this; this.id = id; this.dom = document.createElement('div'); this.dom.innerHTML = '<span>文件名稱:' + this.fileName + ',文件大小:' + this.fileSize + '</span>' + '<button class="delFile">刪除</button>'; this.dom.querySelector('.delFile').onclick = function () { that.delFile(); } document.body.appendChild(this.dom); }; Upload.prototype.delFile = function () { if (this.fileSize < 3000) { return this.dom.parentNode.removeChild(this.dom); } if (window.confirm('肯定刪除該文件嗎?' + this.fileName)) { return this.dom.parentNode.removeChild(this.dom); } }; // 上傳 startUpload('plugin', [ { fileName: '1.txt', fileSize: 1000 }, { fileName: '2.txt', fileSize: 3000 }, { fileName: '3.txt', fileSize: 5000 }, ]); startUpload('flash', [ { fileName: '4.txt', fileSize: 1000 }, { fileName: '5.txt', fileSize: 3000 }, { fileName: '6.txt', fileSize: 5000 }, ]);
享元模式重構文件上傳
var Upload = function (uploadType) { this.uploadType = uploadType; }; Upload.prototype.delFile = function (id) { uploadManager.setExternalState(id, this); if (this.fileSize < 3000) { return this.dom.parentNode.removeChild(this.dom); } if (window.confirm('肯定刪除該文件嗎?' + this.fileName)) { return this.dom.parentNode.removeChild(this.dom); } }; var UploadFactory = (function () { var createdFlyWeightObjs = {}; return { create: function (uploadType) { if (createdFlyWeightObjs[uploadType]) { return createdFlyWeightObjs[uploadType]; } return createdFlyWeightObjs[uploadType] = new Upload(uploadType); } } })(); var uploadManager = (function () { var uploadDatabase = {}; return { add: function (id, uploadType, fileName, fileSize) { var flyWeightObj = UploadFactory.create(uploadType); var dom = document.createElement('div'); dom.innerHTML = '<span>文件名稱:' + fileName + ',文件大小:' + fileSize + '</span>' + '<button class="delFile">刪除</button>'; dom.querySelector('.delFile').onclick = function () { flyWeightObj.delFile(id); }; document.body.appendChild(dom); uploadDatabase[id] = { fileName: fileName, fileSize: fileSize, dom: dom, }; return flyWeightObj; }, setExternalState: function (id, flyWeightObj) { var uploadData = uploadDatabase[id]; for (var key in uploadData) { flyWeightObj[key] = uploadData[key]; } } } })(); var id = 0; window.startUpload = function (uploadType, files) { for (var i = 0; i < files.length; i++) { var file = files[i]; var uploadObj = uploadManager.add(++this.id, uploadType, file.fileName, file.fileSize); } }; // 上傳 startUpload('plugin', [ { fileName: '1.txt', fileSize: 1000 }, { fileName: '2.txt', fileSize: 3000 }, { fileName: '3.txt', fileSize: 5000 }, ]); startUpload('flash', [ { fileName: '4.txt', fileSize: 1000 }, { fileName: '5.txt', fileSize: 3000 }, { fileName: '6.txt', fileSize: 5000 }, ]);
享元模式重構後,不管上傳多少次,Upload 對象(內部狀態)的數量一直是 2。
使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係,將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理它爲止。
var order500 = function (orderType, pay, stock) { if (orderType === 1 && pay === true) { console.log('500 元定金預購,獲得 100 優惠券'); } else { return 'nextSuccessor'; } }; var order200 = function (orderType, pay, stock) { if (orderType === 2 && pay === true) { console.log('200 元定金預購,獲得 50 優惠券'); } else { return 'nextSuccessor'; } }; var orderNormal = function (orderType, pay, stock) { if (stock > 0) { console.log('普通購買,無優惠券'); } else { console.log('手機庫存不足'); } }; var Chain = function (fn) { this.fn = fn; this.successor = null; }; Chain.prototype.setNextSuccessor = function (successor) { return this.successor = successor; }; Chain.prototype.passRequest = function () { var ret = this.fn.apply(this, arguments); if (ret === 'nextSuccessor') { return this.successor && this.successor.passRequest.apply(this.successor, arguments); } return ret; }; var chainOrder500 = new Chain(order500); var chainOrder200 = new Chain(order200); var chainOrderNormal = new Chain(orderNormal); chainOrder500.setNextSuccessor(chainOrder200); chainOrder200.setNextSuccessor(chainOrderNormal); // 使用 chainOrder500.passRequest(1, true, 500); // 輸出:500 元定金預購,獲得 100 優惠券 chainOrder500.passRequest(2, true, 500); // 輸出:200 元定金預購,獲得 50 優惠券 chainOrder500.passRequest(3, true, 500); // 輸出:普通購買,無優惠券 chainOrder500.passRequest(1, false, 0); // 輸出:手機庫存不足
用 AOP 實現指責鏈
Function.prototype.after = function (fn) { var self = this; return function () { var ret = self.apply(this, arguments); if (ret === 'nextSuccessor') { return fn.apply(this, arguments); } return ret; } }; var order = order500.after(order200).after(orderNormal); order(1, true, 500); // 輸出:500 元定金預購,獲得 100 優惠券 order(2, true, 500); // 輸出:200 元定金預購,獲得 50 優惠券 order(1, false, 500); // 輸出:普通購買,無優惠券
中介者模式的做用就是解除對象與對象之間的緊耦合關係。增長一箇中介者對象後,全部的 相關對象都經過中介者對象來通訊,而不是互相引用,因此當一個對象發生改變時,只須要通知 中介者對象便可。
變成了
爲對象動態加入行爲。裝飾者模式常常會造成一條長長的裝飾鏈。
面向對象裝飾者模式
// 原始的飛機類 var Plane = function () { } Plane.prototype.fire = function () { console.log('發射普通子彈'); }; // 接下來增長兩個裝飾類,分別是導彈和原子彈: var MissileDecorator = function (plane) { this.plane = plane; }; MissileDecorator.prototype.fire = function () { this.plane.fire(); console.log('發射導彈'); }; var AtomDecorator = function (plane) { this.plane = plane; }; AtomDecorator.prototype.fire = function () { this.plane.fire(); console.log('發射原子彈'); }; // 運行 var plane = new Plane(); plane = new MissileDecorator(plane); plane = new AtomDecorator(plane); plane.fire(); // 分別輸出: 發射普通子彈、發射導彈、發射原子彈
JavaScript 裝飾者模式
var plane = { fire: function () { console.log('發射普通子彈'); } }; var missileDecorator = function () { console.log('發射導彈'); }; var atomDecorator = function () { console.log('發射原子彈'); }; var fire1 = plane.fire; plane.fire = function () { fire1(); missileDecorator(); }; var fire2 = plane.fire; plane.fire = function () { fire2(); atomDecorator(); }; plane.fire(); // 分別輸出: 發射普通子彈、發射導彈、發射原子彈
狀態模式的關鍵是區分事物內部的狀態,事物內部狀態的改變每每會帶來事物的行爲改變。
例子:電燈的 弱光、強光、關燈 切換。
// OffLightState: var OffLightState = function (light) { this.light = light; }; OffLightState.prototype.buttonWasPressed = function () { console.log('弱光'); // offLightState 對應的行爲 this.light.setState(this.light.weakLightState); // 切換狀態到 weakLightState }; // WeakLightState: var WeakLightState = function (light) { this.light = light; }; WeakLightState.prototype.buttonWasPressed = function () { console.log('強光'); // weakLightState 對應的行爲 this.light.setState(this.light.strongLightState); // 切換狀態到 strongLightState }; // StrongLightState: var StrongLightState = function (light) { this.light = light; }; StrongLightState.prototype.buttonWasPressed = function () { console.log('關燈'); // strongLightState 對應的行爲 this.light.setState(this.light.offLightState); // 切換狀態到 offLightState }; var Light = function () { this.offLightState = new OffLightState(this); this.weakLightState = new WeakLightState(this); this.strongLightState = new StrongLightState(this); this.button = null; }; Light.prototype.init = function () { var button = document.createElement('button'), self = this; this.button = document.body.appendChild(button); this.button.innerHTML = '開關'; this.currState = this.offLightState; // 設置當前狀態 this.button.onclick = function () { self.currState.buttonWasPressed(); } }; Light.prototype.setState = function (newState) { this.currState = newState; }; var light = new Light(); light.init();
適配器模式的做用是解決兩個軟件實體間的接口不兼容的問題。
var googleMap = { show: function () { console.log('開始渲染谷歌地圖'); } }; var baiduMap = { display: function () { console.log('開始渲染百度地圖'); } }; // 添加百度地圖適配器 var baiduMapAdapter = { show: function () { return baiduMap.display(); } }; renderMap(googleMap); // 輸出:開始渲染谷歌地圖 renderMap(baiduMapAdapter); // 輸出:開始渲染百度地圖