基於 javascript 的常用設計模式的實現
- 設計模式是語言編程的一種技巧,提高的代碼優雅性,可讀性,也是爲了提供代碼性能。接下來爲大家介紹幾種常見的設計模式及幾種非常相似的設計放在一起去對比。
單例模式
-
定義: 保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。
-
場景: 登錄按鈕點擊出現登錄浮窗,只會被創建一次
-
代碼示例:
var Transparent = (function(){ var ins = null; var S = function(name){ this.name = name; if(ins){ return ins } return ins = this } S.prototype.init =function(){ } return S })() console.log(new Transparent('ldy2') === new Transparent('ldy3')) // true
-
分析: 通過上面代碼執行結果,可見這種,通過 new 關鍵字創建的實例,也是可以是同一種實例,這也就是單例模式特別重要的一點,還有一種單例模式叫做惰性單例,也是頁面加載後不創建需要執行的時候創建。此處不過多贅述。
迭代器模式
- 定義: 指提供一種方法順序訪問一個聚合對象中的各個元素,而又不需要暴露該對象的內部表示
- 場景: 迭代器模式可以把迭代的過程從業務邏輯中分離出來,在使用迭代器模式之後,使不關心對象的內部構造,也可以按順序訪問其中的每個元素
- 種類: 內部迭代器和外部迭代器;
- 內部迭代器:不需要關心內部的具體實現上面的 foreach 是屬於內部迭代器
- 外部迭代器:必須顯式地請求迭代下一個元素;將下一個迭代給暴露出來
- 代碼示例:
var foreach = function(arr, fn){ var index = 0; while(index !==arr.length){ if(fn){ fn(arr[index], index, arr); }else{ throw new Error(`${fn} is not function!`) } index++ } };
- 分析: 上面的迭代器隸屬內部迭代器,而外部迭代器,就是將 index 的迭代元素給暴露出來,屬於迭代器模式那就是 jquery 的實現,像
$.each
,$.css
等,都是迭代器的最佳實踐
發佈-訂閱模式
-
定義: 又叫觀察者模式,對象間的一種一對多的依賴關係,當一個對象的狀 態發生改變時,所有依賴於它的對象都將得到通知。
-
優勢: 一爲時間上的解耦,二爲對象之間的解耦。從架構上講像 mvvm 及 mvc 都少不了該設計模式參與
-
劣勢: 創建訂閱者本身要消耗一定的時間和內存,當訂閱的消息有木有發生,他都會佔據內存,也是爲了弱化對象間的聯繫,過度使用讓聯繫隱藏,產生 bug,處理並不輕鬆。
-
代碼示例:
// 通用發佈-訂閱 var event = { cacheListen:{}, listen:function(key, fn){ var cache = this.cacheListen[key]; if(!cache){ this.cacheListen[key] = []; } this.cacheListen[key].push(fn); }, trigger:function(){ var key = Array.prototype.slice.call(arguments, 0,1); var cache = this.cacheListen[key]; if(!cache || cache.length ===0){ return false } for (var index = 0; index < cache.length; index++) { cache[index].apply(this, arguments) } }, cancel:function(key, fn){ var cache = this.cacheListen[key] if(!cache){// 無人訂閱 return false; } if(!fn){ // 無回調取消所有事件 cache && (cache.length =0); return false; } for(var i = cache.length-1; i>0; i--){ if(cache[i] === fn){ cache.splice(i,1) } } } } var installEvent = function(obj){ //安裝事件 for(var i in event){ obj[i] = event[i] } } var names = {} installEvent(names) names.listen('liufamliy', function(v, s){ console.log(v, s) }) names.trigger('liufamliy', 50)
命令模式
-
定義: 最簡單和優雅的模式之一,是一個執行某些特定事情的指令,是回調(callback)函數的一個面向對象的替代品
-
場景: 不知請求者是誰,也不知道發送者是誰;使得請求發送者和請求接受者能夠消除彼此之前的耦合關係。可以使用高階函數很方便的能實現
-
代碼示例:
var closeDoorCommand = { execute:function(){ console.log('close') } } var openDoorCommand = { execute:function(){ console.log('open') } } var openQQCommand = { execute:function(){ console.log('qq') } } // 創建宏命令 var MacroCommand = function(){ return { commandList:[], add:function(command){ this.commandList.push(command) }, execute:function(){ for (var index =0, commandList = this.commandList; index < commandList.length; index++) { commandList[index].execute(); } }, undo:function(obj){ if(!obj){// 全部撤銷 this.commandList = [] } for (var index = 0, commandList = this.commandList; index < commandList.length; index++) { if(commandList[index] === obj){ this.commandList.splice(index, 1) } } } } } var macroCommand = MacroCommand(); macroCommand.add(closeDoorCommand) // close macroCommand.add(openDoorCommand) // open macroCommand.add(openQQCommand) macroCommand.undo(openQQCommand); macroCommand.execute()
適配器模式
-
定義: 包裝器, 解決兩個軟件實體間的接口不兼容的問題
-
場景: 新老數據接口不匹配的問題,需要通過適配器去修改,不需要修改老接口;
function cities() { return [{name:'1', id:1},{name:'2', id:2}] } var render = function(fn){ console.log('渲染'); document.write( JSON.stringify(fn())) } var adapterCities = function(oldFn){ var oldA = oldFn(); var a = {}; for (let index = 0; index < oldA.length; index++) { a[oldA[index].name] = oldA[index].id } return function(){ return a } } // render(adapterCities(cities))
享元模式
-
定義: 用於性能優化的模式,使用大量相似的對象,對象大量的狀態都可以剝離出來成外部狀態,可以使用較少的共享對象去取代大量對象
-
爲創建一個對象原狀態,可以其他方法共享操作,避免大量對象的創建
// 對象池的創建 var toolPool = (function(){ var pool = []; return { create:function(){ if(pool.length===0){ var div = document.createElement('div'); document.body.appendChild(div); return div; }else{ return pool.shift() } }, recover:function(toolDom){ return pool.push(toolDom); } } })() var arr = []; for (var index = 0, o = ['A',"B"]; index < o.length; index++) { var d = toolPool.create() d.innerHTML = o[index] arr.push(d); } // console.log(arr) // 回收 for (var i = 0; i < arr.length; i++) { toolPool.recover(arr[i]); }
裝飾者模式 和 代理模式
-
裝飾者和代理模式是一對雙胞胎模式,具有相似性
-
裝飾者: 對象動態地增加職責的方式
-
代理模式: 爲一個對象提供一個代用品或佔位符,以便控制對它的訪問
-
區別:
- 代理和本體保持接口一致,強調代理與實體一種關係,通常只有一層代理 本體的引用。
- 裝飾者模式經常會形成一條長長的裝飾鏈
-
裝飾者示例:
var p = { fire:function(){ console.log('飛') } } var fire1 = p.fire; var autoFire = function(){ console.log('飛得高') } p.fire = function(){ fire1(); autoFire(); } p.fire();
-
代理模式示例:
// 緩存代理 var mul = function(){ var a = 1; for (let index = 0; index < arguments.length; index++) { a *=arguments[index]; } return a; } var createProxy = function(fn){ var cache ={}; return function(){ var args = Array.prototype.join.call(arguments, ','); if(args in cache){ return cache[args]; } return cache[args] = fn.apply(this, arguments) } } var min = createProxy(mul); var j = min(1,2,3,4) console.log(j) //24
策略模式
- 定義: 定義一系列的算法,把它們一個個封裝起來,並且使它們可以相互替換
- 場景: form 表單驗證, 採用 switch case 這種也更適合策略模式
var obj = { A:function(s){ return s*3 }, B:function(s){ return s*4 }, c:function(s){ return s*5 } }
狀態模式
-
定義: 允許一個對象在其內部狀態改變時改變它的行爲,對象看起來似乎修改了它的類
-
場景: 區分事物內部的狀態,事物內部狀態的改變往往會帶來事物的行爲改變
var OpenLightState = function(light){ this.light = light; } OpenLightState.prototype.open = function(){ console.log('開燈') this.light.setState(this.light.closeLight) } var CloseLight = function(light){ this.light = light; } CloseLight.prototype.close = function(){ console.log('關燈') this.light.setState(this.light.openLight) } var Lights = function(){ this.openState = new OpenLightState(this); this.closeState = new CloseLight(this); this.curState = null; } Lights.prototype.setState = function(state){ this.curState = state; } Lights.prototype.init = function(){ // this.curState = this.closeState this.curState = this.openState; this.curState.open(); }
- 分析: 關鍵是這狀態管理,其實可以採用 ifelse 語句,去實現,感覺代碼會產生不友好的狀態。像這種採用策略模式,就不會實現一個狀態管理,狀態更加實用於大型邏輯業務實現。
參考文檔
- javascript 設計模式與開發實踐