這篇文章為大家帶來了關於JavaScript中設計模式的相關知識,其中包括單例模式、組合模式、觀察者模式等等,希望對大家有幫助。
#設計模式是為了解決某種問題,而設計一套最佳的解決方案。
物件導向- 更重視物件- 找一個最佳的定義物件並個物件新增屬性和方法的方案
找到的最佳的解決方案- 就是一種設計模式
針對不同的問題,我們會有不同的最佳解決方案- 設計模式
常見的設計模式:
資料庫連線- 多個功能都需要操作資料庫- 重新連接資料庫- 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><hr> <h3>1.4 發布訂閱模式</h3> <p>https://blog.csdn.net/weixin_44070254/article/details/117574565 ?spm=1001.2014.3001.5501</p> <p>有人訂閱有人發布<br> 發布訂閱模式:需要一個觀察者需要一個被觀察者如果觀察者發現被觀察者的狀態發生了改變,就需要執行一個任務<br> 定義一個觀察者:</p> <pre class="brush:php;toolbar:false">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><p>觀察者模式,又稱為發布-訂閱模式。意思是讓一個人不停的監控某件某件東西,當這個東西要發生某種行為的時候,這個人就通知一個函數執行這個行為的操作。 </p> <p>範例:當事件的程式碼寫好以後,其實這個事件就不停的監控使用者在頁面中的行為,一旦使用者觸發這個事件的時候,就呼叫函數處理這個事件。 </p> <pre class="brush:php;toolbar:false">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><p>测试:</p> <table> <thead><tr class="firstRow"><th>触发测试</th></tr></thead> <tbody> <tr><td><img src="https://img.php.cn/upload/article/000/000/067/723219bbf42dba49705fd0fe49beab7d-1.png" alt="JavaScript經典講解之設計模式(實例詳解)"></td></tr> <tr><td><br></td></tr> </tbody> </table> <p>这两个处理函数都没有参数,如果要传入参数的时候该怎么处理?</p> <p>触发事件的时候就要传入实参</p> <pre class="brush:php;toolbar:false">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><p>添加一个带参数的处理函数,并触发事件执行:</p> <pre class="brush:php;toolbar:false">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><p>解绑测试:</p> <table> <thead><tr class="firstRow"><th>解绑测试结果</th></tr></thead> <tbody> <tr><td><img src="https://img.php.cn/upload/article/000/000/067/723219bbf42dba49705fd0fe49beab7d-3.png" alt="JavaScript經典講解之設計模式(實例詳解)"></td></tr> <tr><td><br></td></tr> </tbody> </table> <p>如果绑定事件的时候使用的匿名函数,就无法进行解绑了,所以再添加一个解绑事件所有处理函数的方法:</p> <pre class="brush:php;toolbar:false">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{ 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学习教程
以上是JavaScript經典講解之設計模式(實例詳解)的詳細內容。更多資訊請關注PHP中文網其他相關文章!