自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

JavaScript 的一些常用設(shè)計模式

開發(fā) 前端
設(shè)計模式的定義:在面向?qū)ο筌浖O(shè)計過程中針對特定問題的簡潔而優(yōu)雅的解決方案

設(shè)計模式是前人解決某個特定場景下對而總結(jié)出來的一些解決方案??赡軇傞_始接觸編程還沒有什么經(jīng)驗(yàn)的時候,會感覺設(shè)計模式?jīng)]那么好理解,這個也很正常。有些簡單的設(shè)計模式我們有時候用到,不過沒意識到也是存在的。

[[274128]]

學(xué)習(xí)設(shè)計模式,可以讓我們在處理問題的時候提供更多更快的解決思路。

當(dāng)然設(shè)計模式的應(yīng)用也不是一時半會就會上手,很多情況下我們編寫的業(yè)務(wù)邏輯都沒用到設(shè)計模式或者本來就不需要特定的設(shè)計模式。

適配器模式

這個使我們常使用的設(shè)計模式,也算最簡單的設(shè)計模式之一,好處在于可以保持原有接口的數(shù)據(jù)結(jié)構(gòu)不變動。

適配器模式(Adapter Pattern)是作為兩個不兼容的接口之間的橋梁。

例子

適配器模式很好理解,假設(shè)我們和后端定義了一個接口數(shù)據(jù)結(jié)構(gòu)為(可以理解為舊接口):

  1.   { 
  2.     "label""選擇一"
  3.     "value": 0 
  4.   }, 
  5.   { 
  6.     "label""選擇二"
  7.     "value": 1 
  8.   } 

但是后端后面因?yàn)槠渌颍枰x返回的結(jié)構(gòu)為(可以理解為新接口):

  1.   { 
  2.     "label""選擇一"
  3.     "text": 0 
  4.   }, 
  5.   { 
  6.     "label""選擇二"
  7.     "text": 1 
  8.   } 

然后我們前端的使用到后端接口有好幾處,那么我可以把新的接口字段結(jié)構(gòu)適配為老接口的,就不需要各處去修改字段,只要把源頭的數(shù)據(jù)適配好就可以了。

當(dāng)然上面的是非常簡單的場景,也是經(jīng)常用到的場景。或許你會認(rèn)為后端處理不更好了,的確是這樣更好,但是這個不是我們討論的范圍。

單例模式

單例模式,從字面意思也很好理解,就是實(shí)例化多次都只會有一個實(shí)例。

有些場景實(shí)例化一次,可以達(dá)到緩存效果,可以減少內(nèi)存占用。還有些場景就是必須只能實(shí)例化一次,否則實(shí)例化多次會覆蓋之前的實(shí)例,導(dǎo)致出現(xiàn) bug(這種場景比較少見)。

例子

實(shí)現(xiàn)彈框的一種做法是先創(chuàng)建好彈框, 然后使之隱藏, 這樣子的話會浪費(fèi)部分不必要的 DOM 開銷, 我們可以在需要彈框的時候再進(jìn)行創(chuàng)建, 同時結(jié)合單例模式實(shí)現(xiàn)只有一個實(shí)例, 從而節(jié)省部分 DOM 開銷。下列為登入框部分代碼:

  1. const createLoginLayer = function() { 
  2.   const div = document.createElement('div'
  3.   div.innerHTML = '登入浮框' 
  4.   div.style.display = 'none' 
  5.   document.body.appendChild(div) 
  6.   return div 

使單例模式和創(chuàng)建彈框代碼解耦

  1. const getSingle = function(fn) { 
  2.   const result 
  3.   return function() { 
  4.     return result || result = fn.apply(this, arguments) 
  5.   } 
  6. const createSingleLoginLayer = getSingle(createLoginLayer) 
  7.  
  8. document.getElementById('loginBtn').onclick = function() { 
  9.   createSingleLoginLayer() 

代理模式

代理模式的定義:為一個對象提供一個代用品或占位符,以便控制對它的訪問。

代理對象擁有本體對象的一切功能的同時,可以擁有而外的功能。而且代理對象和本體對象具有一致的接口,對使用者友好。

虛擬代理

下面這段代碼運(yùn)用代理模式來實(shí)現(xiàn)圖片預(yù)加載,可以看到通過代理模式巧妙地將創(chuàng)建圖片與預(yù)加載邏輯分離,,并且在未來如果不需要預(yù)加載,只要改成請求本體代替請求代理對象就行。

  1. const myImage = (function() { 
  2.   const imgNode = document.createElement('img'
  3.   document.body.appendChild(imgNode) 
  4.   return { 
  5.     setSrc: function(src) { 
  6.       imgNode.src = src 
  7.     } 
  8.   } 
  9. })() 
  10.  
  11. const proxyImage = (function() { 
  12.   const img = new Image() 
  13.   img.onload = function() { // http 圖片加載完畢后才會執(zhí)行 
  14.     myImage.setSrc(this.src) 
  15.   } 
  16.   return { 
  17.     setSrc: function(src) { 
  18.       myImage.setSrc('loading.jpg') // 本地 loading 圖片 
  19.       img.src = src 
  20.     } 
  21.   } 
  22. })() 
  23.  
  24. proxyImage.setSrc('http://loaded.jpg'

緩存代理

在原有的功能上加上結(jié)果緩存功能,就屬于緩存代理。

原先有個功能是實(shí)現(xiàn)字符串反轉(zhuǎn)(reverseString),那么在不改變 reverseString 的現(xiàn)有邏輯,我們可以使用緩存代理模式實(shí)現(xiàn)性能的優(yōu)化,當(dāng)然也可以在值改變的時候去處理下其他邏輯,如 Vue computed 的用法。

  1. function reverseString(str) { 
  2.   return str 
  3.     .split(''
  4.     .reverse() 
  5.     .join(''
  6. const reverseStringProxy = (function() { 
  7.   const cached = {} 
  8.   return function(str) { 
  9.     if (cached[str]) { 
  10.       return cached[str] 
  11.     } 
  12.     cached[str] = reverseString(str) 
  13.     return cached[str] 
  14.   } 
  15. })() 

訂閱發(fā)布模式

訂閱發(fā)布使前端常用的數(shù)據(jù)通信方式、異步邏輯處理等等,如 React setState 和 Redux 就是訂閱發(fā)布模式的。

但是要合理的使用訂閱發(fā)布模式,否則會造成數(shù)據(jù)混亂,redux 的單向數(shù)據(jù)流思想可以避免數(shù)據(jù)流混亂的問題。

例子

  1. class Event { 
  2.   constructor() { 
  3.     // 所有 eventType 監(jiān)聽器回調(diào)函數(shù)(數(shù)組) 
  4.     this.listeners = {} 
  5.   } 
  6.   /** 
  7.    * 訂閱事件 
  8.    * @param {String} eventType 事件類型 
  9.    * @param {Function} listener 訂閱后發(fā)布動作觸發(fā)的回調(diào)函數(shù),參數(shù)為發(fā)布的數(shù)據(jù) 
  10.    */ 
  11.   on(eventType, listener) { 
  12.     if (!this.listeners[eventType]) { 
  13.       this.listeners[eventType] = [] 
  14.     } 
  15.     this.listeners[eventType].push(listener) 
  16.   } 
  17.   /** 
  18.    * 發(fā)布事件 
  19.    * @param {String} eventType 事件類型 
  20.    * @param {Any} data 發(fā)布的內(nèi)容 
  21.    */ 
  22.   emit(eventType, data) { 
  23.     const callbacks = this.listeners[eventType] 
  24.     if (callbacks) { 
  25.       callbacks.forEach((c) => { 
  26.         c(data) 
  27.       }) 
  28.     } 
  29.   } 
  30.  
  31. const event = new Event() 
  32. event.on('open', (data) => { 
  33.   console.log(data) 
  34. }) 
  35. event.emit('open', { opentrue }) 

觀察者模式

觀察者模式定義了一種一對多的依賴關(guān)系,讓多個觀察者對象同時監(jiān)聽某一個目標(biāo)對象,當(dāng)這個目標(biāo)對象的狀態(tài)發(fā)生變化時,會通知所有觀察者對象,使它們能夠自動更新。

Vue 的數(shù)據(jù)驅(qū)動就是使用觀察者模式,mbox 也是使用觀察者模式。

例子

模仿 Vue 數(shù)據(jù)驅(qū)動渲染模式(只是類似,簡單的模仿)。

首先使用 setter 和 getter 監(jiān)聽到數(shù)據(jù)的變化:

  1. const obj = { 
  2.   data: { description: '' }, 
  3.  
  4. Object.defineProperty(obj, 'description', { 
  5.   get() { 
  6.     return this.data.description 
  7.   }, 
  8.   set(val) { 
  9.     this.data.description = val 
  10.   }, 
  11. }) 

然后加上目標(biāo)和觀察者

  1. class Subject { 
  2.   constructor() { 
  3.     this.observers = [] 
  4.   } 
  5.  
  6.   add(observer) { 
  7.     this.observers.push(observer) 
  8.   } 
  9.  
  10.   notify(data) { 
  11.     this.observers.forEach((observer) => observer.update(data)) 
  12.   } 
  13.  
  14. class Observer { 
  15.   constructor(callback) { 
  16.     this.callback = callback 
  17.   } 
  18.   update(data) { 
  19.     this.callback && this.callback(data) 
  20.   } 
  21.  
  22. // 創(chuàng)建觀察者ob1 
  23. let ob1 = new Observer((text) => { 
  24.   document.querySelector('#dom-one').innerHTML(text) 
  25. }) 
  26. // 創(chuàng)建觀察者ob2 
  27. let ob2 = new Observer((text) => { 
  28.   document.querySelector('#dom-two').innerHTML(text) 
  29. }) 
  30. // 創(chuàng)建目標(biāo)sub 
  31. let sub = new Subject() 
  32. // 目標(biāo)sub添加觀察者ob1 (目標(biāo)和觀察者建立了依賴關(guān)系) 
  33. sub.add(ob1) 
  34. // 目標(biāo)sub添加觀察者ob2 
  35. sub.add(ob2) 
  36. // 目標(biāo)sub觸發(fā)事件(目標(biāo)主動通知觀察者) 
  37. sub.notify('這里改變了'

組合在一起是這樣的

  1. <!DOCTYPE html> 
  2. <html> 
  3.   <head> 
  4.     <meta charset="utf-8" /> 
  5.     <meta 
  6.       name="viewport" 
  7.       content="width=device-width,initial-scale=1,maximum-scale=1,viewport-fit=cover" 
  8.     /> 
  9.     <title></title> 
  10.   </head> 
  11.   <body> 
  12.     <div id="app"
  13.       <div id="dom-one"
  14.         原來的值 
  15.       </div> 
  16.       <br /> 
  17.       <div id="dom-two"
  18.         原來的值 
  19.       </div> 
  20.       <br /> 
  21.       <button id="btn">改變</button> 
  22.     </div> 
  23.     <script> 
  24.       class Subject { 
  25.         constructor() { 
  26.           this.observers = [] 
  27.         } 
  28.  
  29.         add(observer) { 
  30.           this.observers.push(observer) 
  31.         } 
  32.  
  33.         notify() { 
  34.           this.observers.forEach((observer) => observer.update()) 
  35.         } 
  36.       } 
  37.  
  38.       class Observer { 
  39.         constructor(callback) { 
  40.           this.callback = callback 
  41.         } 
  42.         update() { 
  43.           this.callback && this.callback() 
  44.         } 
  45.       } 
  46.  
  47.       const obj = { 
  48.         data: { description: '' }, 
  49.       } 
  50.  
  51.       // 創(chuàng)建觀察者ob1 
  52.       const ob1 = new Observer(() => { 
  53.         console.log(document.querySelector('#dom-one')) 
  54.         document.querySelector('#dom-one').innerHTML = obj.description 
  55.       }) 
  56.       // 創(chuàng)建觀察者ob2 
  57.       const ob2 = new Observer(() => { 
  58.         document.querySelector('#dom-two').innerHTML = obj.description 
  59.       }) 
  60.       // 創(chuàng)建目標(biāo)sub 
  61.       const sub = new Subject() 
  62.       // 目標(biāo)sub添加觀察者ob1 (目標(biāo)和觀察者建立了依賴關(guān)系) 
  63.       sub.add(ob1) 
  64.       // 目標(biāo)sub添加觀察者ob2 
  65.       sub.add(ob2) 
  66.  
  67.       Object.defineProperty(obj, 'description', { 
  68.         get() { 
  69.           return this.data.description 
  70.         }, 
  71.         set(val) { 
  72.           this.data.description = val 
  73.           // 目標(biāo)sub觸發(fā)事件(目標(biāo)主動通知觀察者) 
  74.           sub.notify() 
  75.         }, 
  76.       }) 
  77.       btn.onclick = () => { 
  78.         obj.description = '改變了' 
  79.       } 
  80.     </script> 
  81.   </body> 
  82. </html> 

裝飾者模式

裝飾器模式(Decorator Pattern)允許向一個現(xiàn)有的對象添加新的功能,同時又不改變其結(jié)構(gòu)。

ES6/7 的decorator 語法提案,就是裝飾者模式。

例子

  1. class A { 
  2.   getContent() { 
  3.     return '第一行內(nèi)容' 
  4.   } 
  5.   render() { 
  6.     document.body.innerHTML = this.getContent() 
  7.   } 
  8.  
  9. function decoratorOne(cla) { 
  10.   const prevGetContent = cla.prototype.getContent 
  11.   cla.prototype.getContent = function() { 
  12.     return ` 
  13.       第一行之前的內(nèi)容 
  14.       <br/> 
  15.       ${prevGetContent()} 
  16.     ` 
  17.   } 
  18.   return cla 
  19.  
  20. function decoratorTwo(cla) { 
  21.   const prevGetContent = cla.prototype.getContent 
  22.   cla.prototype.getContent = function() { 
  23.     return ` 
  24.       ${prevGetContent()} 
  25.       <br/> 
  26.       第二行內(nèi)容 
  27.     ` 
  28.   } 
  29.   return cla 
  30.  
  31. const B = decoratorOne(A) 
  32. const C = decoratorTwo(B) 
  33. new C().render() 

策略模式

在策略模式(Strategy Pattern)中,一個行為或其算法可以在運(yùn)行時更改。

假設(shè)我們的績效分為 A、B、C、D 這四個等級,四個等級的獎勵是不一樣的,一般我們的代碼是這樣實(shí)現(xiàn):

  1. /** 
  2.  
  3. * 獲取年終獎 
  4.  
  5. * @param {String} performanceType 績效類型, 
  6.  
  7. * @return {Object} 年終獎,包括獎金和獎品 
  8.  
  9. */ 
  10.  
  11. function getYearEndBonus(performanceType) { 
  12.  
  13. const yearEndBonus = { 
  14.  
  15. // 獎金 
  16.  
  17. bonus: ''
  18.  
  19. // 獎品 
  20.  
  21. prize: ''
  22.  
  23.  
  24. switch (performanceType) { 
  25.  
  26. case 'A': { 
  27.  
  28. yearEndBonus = { 
  29.  
  30. bonus: 50000, 
  31.  
  32. prize: 'mac pro'
  33.  
  34.  
  35. break 
  36.  
  37.  
  38. case 'B': { 
  39.  
  40. yearEndBonus = { 
  41.  
  42. bonus: 40000, 
  43.  
  44. prize: 'mac air'
  45.  
  46.  
  47. break 
  48.  
  49.  
  50. case 'C': { 
  51.  
  52. yearEndBonus = { 
  53.  
  54. bonus: 20000, 
  55.  
  56. prize: 'iphone xr'
  57.  
  58.  
  59. break 
  60.  
  61.  
  62. case 'D': { 
  63.  
  64. yearEndBonus = { 
  65.  
  66. bonus: 5000, 
  67.  
  68. prize: 'ipad mini'
  69.  
  70.  
  71. break 
  72.  
  73.  
  74.  
  75. return yearEndBonus 
  76.  

使用策略模式可以這樣:

  1. /** 
  2.  * 獲取年終獎 
  3.  * @param {String} strategyFn 績效策略函數(shù) 
  4.  * @return {Object} 年終獎,包括獎金和獎品 
  5.  */ 
  6. function getYearEndBonus(strategyFn) { 
  7.   if (!strategyFn) { 
  8.     return {} 
  9.   } 
  10.   return strategyFn() 
  11.  
  12. const bonusStrategy = { 
  13.   A() { 
  14.     return { 
  15.       bonus: 50000, 
  16.       prize: 'mac pro'
  17.     } 
  18.   }, 
  19.   B() { 
  20.     return { 
  21.       bonus: 40000, 
  22.       prize: 'mac air'
  23.     } 
  24.   }, 
  25.   C() { 
  26.     return { 
  27.       bonus: 20000, 
  28.       prize: 'iphone xr'
  29.     } 
  30.   }, 
  31.   D() { 
  32.     return { 
  33.       bonus: 10000, 
  34.       prize: 'ipad mini'
  35.     } 
  36.   }, 
  37.  
  38. const performanceLevel = 'A' 
  39. getYearEndBonus(bonusStrategy[performanceLevel]) 

這里每個函數(shù)就是一個策略,修改一個其中一個策略,并不會影響其他的策略,都可以單獨(dú)使用。當(dāng)然這只是個簡單的范例,只為了說明。

策略模式比較明顯的特性就是可以減少 if 語句或者 switch 語句。

職責(zé)鏈模式

顧名思義,責(zé)任鏈模式(Chain of Responsibility Pattern)為請求創(chuàng)建了一個接收者對象的鏈。這種模式給予請求的類型,對請求的發(fā)送者和接收者進(jìn)行解耦。這種類型的設(shè)計模式屬于行為型模式。

在這種模式中,通常每個接收者都包含對另一個接收者的引用。如果一個對象不能處理該請求,那么它會把相同的請求傳給下一個接收者,依此類推。

例子

  1. function order(options) { 
  2.   return { 
  3.     next: (callback) => callback(options), 
  4.   } 
  5.  
  6. function order500(options) { 
  7.   const { orderType, pay } = options 
  8.   if (orderType === 1 && pay === true) { 
  9.     console.log('500 元定金預(yù)購, 得到 100 元優(yōu)惠券'
  10.     return { 
  11.       next: () => {}, 
  12.     } 
  13.   } else { 
  14.     return { 
  15.       next: (callback) => callback(options), 
  16.     } 
  17.   } 
  18.  
  19. function order200(options) { 
  20.   const { orderType, pay } = options 
  21.   if (orderType === 2 && pay === true) { 
  22.     console.log('200 元定金預(yù)購, 得到 50 元優(yōu)惠券'
  23.     return { 
  24.       next: () => {}, 
  25.     } 
  26.   } else { 
  27.     return { 
  28.       next: (callback) => callback(options), 
  29.     } 
  30.   } 
  31.  
  32. function orderCommon(options) { 
  33.   const { orderType, stock } = options 
  34.   if (orderType === 3 && stock > 0) { 
  35.     console.log('普通購買, 無優(yōu)惠券'
  36.     return {} 
  37.   } else { 
  38.     console.log('庫存不夠, 無法購買'
  39.   } 
  40.  
  41. order({ 
  42.   orderType: 3, 
  43.   pay: true
  44.   stock: 500, 
  45. }) 
  46.   .next(order500) 
  47.   .next(order200) 
  48.   .next(orderCommon) 
  49. // 打印出 “普通購買, 無優(yōu)惠券” 

上面的代碼,對 order 相關(guān)的進(jìn)行了解耦,order500,order200、orderCommon 等都是可以單獨(dú)調(diào)用的。

責(zé)任編輯:華軒 來源: segmentfault
相關(guān)推薦

2017-04-08 17:12:36

設(shè)計模式抽象策略模式

2012-10-29 11:16:13

2010-09-28 14:14:19

SQL語句

2011-03-15 17:46:43

2021-10-13 07:48:23

Options模式編程

2011-07-13 09:13:56

Android設(shè)計

2022-12-02 14:58:27

JavaScript技巧編程

2022-02-17 13:58:38

Linux技巧文件

2012-06-07 10:17:55

軟件設(shè)計設(shè)計原則Java

2009-09-27 11:09:42

API設(shè)計

2015-08-27 10:49:43

JavaScript開發(fā)框架

2014-08-14 09:25:31

Linux串口

2012-03-14 14:30:13

Ubuntu軟件包

2021-12-20 10:55:05

Git命令Linux

2020-10-19 19:25:32

Python爬蟲代碼

2014-05-13 09:55:13

iOS開發(fā)工具

2010-10-08 16:32:59

MySQL語句

2021-08-28 11:47:52

json解析

2022-03-22 07:38:00

SQL語句MySQL

2011-09-19 10:15:10

移動界面設(shè)計
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號