設計模式是為了解決某種問題,而設計的一套最佳解決方案。
物件導向 - 更加註重物件 - 找一種最佳的定義物件並個物件新增屬性和方法 的 方案
找到的最佳的解決方案 - 就是一種設計模式
針對不同的問題,我們會有不同的最佳解決方案 - 設計模式
常見的設計模式:
資料庫連線 - 多個功能都需要運算元據庫 - 重新連線資料庫 - 1000個功能,連線1000次資料庫
註冊功能,每次註冊都需要運算元據庫 - 同一時間有1000個人在註冊
mysql資料庫有一個連線限制:最多隻能支援同時又200個連線
我們連線一次資料庫,得到一個連線,多次運算元據庫,使用這一個連線其就能操作
怎麼操作,讓多次執行資料庫語句使用同一個連線 - 我們就可以設計一個設計模式
設計模式:定義一個類,這個類new以後就得到一個連線,以後每次執行資料庫語句的時候,都可以從這個類中得到一個連線
單例模式:從一個類中只能得到一個物件 - 不管操作了多少次這個類,最終得出的所有連線物件都是同一個物件
讓一個類建立出來的所有物件,裡面的所有屬性和方法都一模一樣。比如封裝一個類,將一些常用的操作函數作為方法放進去,以後每次都使用同一個物件來呼叫這些方法
正常情況,一個類建立出來的每個物件都是不一樣的。
class Carousel{ }var a = new Carousel();var b = new Carousel();console.log(a === b); // false
單例模式就是讓這兩個物件是一樣的,也就是說,一個類永遠只有一個範例物件
var single = (function(){ class Carousel{ } var res = undefined; return function(){ if(!res){ res = new Carousel(); } return res; }})();var s1 = single();var s2 = single();console.log(s1 === s2); // true
變數P暴露在全域性的變數會造成全域性的汙染
利用閉包解決每次都會重新定義p的問題
單例模式的核心程式碼:
function Person(){}function fn(Person){ var p; return function(){ if(!p){ p = new Person; } return p; }}var f = fn(Person)var p1 = f()var p2 = f()
單例模式的應用場景在封裝工具庫。
例:封裝封裝一個工具庫
單例模式 應用 封裝工具庫
工具庫中會有很多的方法 - 方法每次的使用 應該是同一個物件使用的,而不是應該每次都有一個新物件在使用工具
// var t = new Tool; // t.setCookie('username', 'zs', 20); const Tool = (function () { class Tool { constructor() { if (window.getComputedStyle) { this.flag = true; } else { this.flag = false; } } /獲取節點屬性 getStyle(ele, attr) { if (this.flag) { return window.getComputedStyle(ele)[attr]; } else { return ele.currentStyle[attr]; } } getStyle(ele, attr) { 嘗試一段程式碼 不知道會不會報錯 嘗試成功 後面程式碼沒有什麼事 嘗試失敗 會報錯 被cathch 捕獲到 會將錯誤資訊放到err引數裡 catch{} 裡可以處理這個錯誤 也可以不處理這個錯誤對上面的錯誤程式碼進行補救 錯誤不會再瀏覽器裡報錯 try { return window.getComputedStyle(ele)[attr]; } catch (err) { return ele.currentStyle[attr]; } } // 設定節點css屬性 setStyle(ele, styleObj) { for (let attr in styleObj) { ele.style[attr] = styleObj[attr]; } } // 設定cookie setCookie(key, value, second, path = '/') { let data = new Date(); date.setTime(date.getTime() - 8 * 3600 * 1000 + second * 1000); document.cookie = `${key}=${value};expires=${date};path=${path}`; } } var tool; return (function () { if (!tool) { tool = new Tool(); } return tool; })(); })();
組合模式就是製作啟動器。多個類在範例化以後,執行起來使用一個同名的方法來啟動,這時候可以做一個啟動器,讓多個類一起啟動。
class Carousel{ init(){ console.log("輪播圖開始執行"); }}class Tab{ init(){ console.log("索引標籤開始執行"); }}class Enlarge{ init(){ console.log("放大鏡開始執行"); }}// 這3個類要執行起來需要各自範例化,並呼叫每個類中的init方法,此時就可以使用組合模式做一個啟動器
組合模式製作啟動器:
class Starter{ constructor(){ this.arr = []; // 定義一個陣列 } add(className){ this.arr.push(className); // 將這個多個類放進陣列 } run(){ for(var i=0;i<this.arr.length;i++){ arr[i].init(); // 讓陣列中的每個類都呼叫init方法 } }}var starts = new Starter();starts.add(new Carousel);starts.add(new Tab);starts.add(new Enlarge);starts.run();
https://blog.csdn.net/weixin_44070254/article/details/117574565?spm=1001.2014.3001.5501
有人訂閱 有人釋出
釋出訂閱模式:需要一個觀察者 需要一個被觀察者 如果觀察者發現被觀察者的狀態發生了改變,就需要執行一個任務
定義一個觀察者:
class Observer{ // 觀察者有名字和任務 constructor(name,task){ this.name = name this.task = task }}// 定義班主任var bzr = new Observer('班主任',function(state){ console.log("因為"+state+",所以"+this.name+"罰站");})// 定義了一個年級主任var zr = new Observer('年級主任',function(state){ console.log("因為"+state+",所以"+this.name+"要找班主任");})// 被觀察者class Subject{ // 有自己的狀態 constructor(state){ this.state = state // 觀察者們 this.observer = []; } // 新增被觀察者 add(...arr){ this.observer = this.observer.concat(arr) } // 改變狀態 changeSate(state){ this.state = state // 觸發觀察者的任務 for(let i=0;i<this.observer.length;i++){ this.observer[i].task(state) } }}var xm = new Subject('學習')xm.add(bzr,zr)// xm.changeSate('摸了一下小紅的手')xm.changeSate('玩遊戲')
觀察者模式,又稱釋出-訂閱模式。意思是讓一個人不停的監控某件某件東西,當這個東西要發生某種行為的時候,這個人就通知一個函數執行這個行為的操作。
例:當事件的程式碼寫好以後,其實這個事件就不停的監控使用者在頁面中的行為,一旦使用者觸發這個事件的時候,就呼叫函數處理這個事件。
p.addEventListener("click",function(){}); // 這個事件寫好以後,就一直在頁面中監控使用者行為,使用者點選這個元素的時候,就呼叫函數
觀察者模式就是類似的操作,寫觀察者模式的目的,是為了給一個非元素的資料繫結一個自定義事件。
例:給一個obj繫結一個abc的事件
分析:
給一個元素繫結事件,有繫結方法,有觸發條件,有取消繫結。
要給一個物件繫結一個自定義事件。那麼這個事件如何繫結,如何觸發,如何解綁這個事件。
所以:
元素的事件,一個事件型別可以繫結多個處理常式。
物件的自定義事件如何讓一個事件型別繫結多個函數。
所以:
需要一個空間,儲存事件型別對應的處理常式們。
class watch{ bind(){ } touch(){ } unbind(){ }}var w = new watch();
此時,如要給這個物件繫結事件和處理常式的話,需要事件型別和處理常式作為引數,所以呼叫時要傳入實參
var w = new watch();w.bind("cl",a); // 給w物件繫結cl事件型別,執行a函數w.bind("cl",b); // 給w物件繫結cl事件型別,執行b函數function a(){ console.log("事件處理常式a");}function b(){ console.log("事件處理常式b");}
在bind方法中接收引數,並將事件型別和處理常式對應儲存起來:
constructor(){ this.obj = {} // 儲存格式:{事件型別:[函數1,函數2]}}bind(type,handle){ this.obj[type] = [handle]; // 物件儲存方式{"cl":[a]}}
如果給這個事件再繫結一個函數b的話,會將原來的a覆蓋掉,所以,應該先判斷,如果對應的這個陣列中沒有資料就直接放進去,如果有了就應該追加
bind(type,handle){ if(!this.obj[type]){ this.obj[type] = [handle]; // 物件儲存方式{"cl":[a]} }else{ this.obj[type].push(handle); } }
列印w物件的結果:
繫結後的儲存結果 |
---|
觸發這個事件需要傳入觸發哪個事件型別
touch(type){ // 首先要判斷,這個事件型別是否繫結,沒有繫結不能觸發 if(!this.obj[type]){ return false; }else{ // 將這個事件的所有繫結的處理常式一起呼叫 for(var i=0;i<this.obj[type].length;i++){ this.obj[type][i](); } }}
測試:
觸發測試 |
---|
這兩個處理常式都沒有引數,如果要傳入引數的時候該怎麼處理?
觸發事件的時候就要傳入實參
w.touch("cl","張三",20);
觸發事件的方法就應該處理這些引數
touch(type,...arr){ // 因為引數不定長,所以使用合併運運算元 // 首先要判斷,這個事件型別是否繫結,沒有繫結不能觸發 if(!this.obj[type]){ return false; }else{ // 處理引數:模擬系統事件的引數事件物件,將所有引數都集中在一個物件中 var e = { type:type, args:arr } // 將這個事件的所有繫結的處理常式一起呼叫 for(var i=0;i<this.obj[type].length;i++){ this.obj[type][i](e); } }}
新增一個帶引數的處理常式,並觸發事件執行:
w.bind("cl",c); // 給w物件繫結cl事件型別,執行c函數w.touch("cl","張三",20);function c(e){ console.log("我是處理常式c,列印:姓名"+e.name+",年齡"+e.age);}
結果:
帶引數的處理常式處理結果 |
---|
解綁也需要知道解綁的事件型別和處理常式
unbind(type,handle){ // 先判斷是否繫結了這個事件 if(!this.obj[type]){ return false; }else{ // 從陣列中將這個處理常式刪除 for(var i=0;i<this.obj[type].length;i++){ if(this.obj[type][i] === type){ this.obj[type].splice(i,1); i--; // 放置陣列塌陷 } } }}
解綁測試:
解綁測試結果 |
---|
如果繫結事件的時候使用的匿名函數,就無法進行解綁了,所以再新增一個解綁事件所有處理常式的方法:
clear(type){ if(!this.obj[type]){ return false; }else{ // 直接從物件中將這個屬性刪除 delete this.obj[type]; } }
觀察者模式跟釋出訂閱模式不一樣的地方在於,要有觀察者和被觀察者。
建立觀察者:
// 建立觀察者class Observer{ // 觀察者有姓名和技能 - 技能是一個函數 constructor(name,skill){ this.name = name; this.skill = skill }}// 建立觀察者var bzr = new Observer('班主任',function(state){ console.log('因為'+state+'叫家長')})var xz = new Observer('校長',function(state){ console.log('因為'+state+'叫班主任')})console.log(bzr,xz)
建立被觀察者:
// 建立被觀察者 class Subject{ // 被觀察者有狀態 和 觀察者列表 constructor(state){ this.state = state this.observers = [] } // 新增觀察者 addObserver(observer){ var index = this.observers.findIndex(v=>v === observer) if(index<0){ this.observers.push(observer) } } // 改變狀態 setState(val){ if(val!==this.state){ this.state = val; this.observers.forEach(v=>{ v.skill(this.state) }) } } // 刪除觀察者 delObserver(observer){ var index = this.observers.findIndex(v=>v === observer) if(index>=0){ this.observers.splice(index,1) } } } // 建立被觀察者 var xm = new Subject('學習') // 新增觀察者 xm.addObserver(bzr) xm.addObserver(xz) xm.addObserver(bzr) console.log(xm) // 改變狀態 xm.setState('玩遊戲')
一個問題有多種解決方案,且隨時還可以有更多的解決方案,就是策略模式。例如:一個商品在售賣的時候,會有多種折扣方式,慢100減10元,滿200減30元等…
商品活動:
滿100減10
滿200減30
滿300打8折
策略模式:將折扣放在閉包中儲存起來 - 通過閉包對摺扣進行增刪改查操作
正常的函數表達方式:
function fn(type,price){ if(type === '100_10'){ return price -= 10 }else if(type === '200_30'){ return price -= 0 }else{ return '沒有這種折扣' }}fn('100_10',160)
這種方式,在新增折扣或刪除折扣的時候總是需要修改原函數。
策略模式程式碼:
var calcPrice = (function(){ // 以閉包的形式儲存摺扣資料 var sale = { '100_10':function(price){ return price -= 10 }, '200_20':function(price){ return price -= 20 } } function calcPrice(type,price){ // 閉包中使用折扣 if(type in sale){ return sale[type](price) }else{ return '沒有這個折扣' } } // 折扣函數新增屬性 - 新增折扣 calcPrice.add = function(type,fn){ if(type in sale){ console.log('折扣存在') }else{ sale[type] = fn } } // 刪除折扣 calcPrice.del = function(type){ if(type in sale){ delete sale[type] }else{ console.log('折扣不存在') } } return calcPrice})()var p = calcPrice('100_10',200)calcPrice.add('200_320',function(price){ return price -= 50})calcPrice.del('200_330')var p = calcPrice('200_320',200)console.log(p)
相關推薦:
以上就是JavaScript經典講解之設計模式(範例詳解)的詳細內容,更多請關注TW511.COM其它相關文章!