日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

JS常用的设计模式

發布時間:2023/12/6 javascript 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JS常用的设计模式 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
持續更新
JS常用的設計模式以及應用場景


*以下內容為個人簡單理解以及摘抄部分網上demo組成,有錯誤請在下方評論指出?*

# 何謂設計模式


沒必要被高大上的名詞所嚇倒,日常coding中或許一個不了解各種設計模式的程序員可能自己其實已經用到了很多
拋開官方的定義在我看來簡單來說就是一個簡單的思想被統一為規范,按照這個規范可以寫出更優雅可控亦或性能更佳的代碼,像是框架的單位
軟件設計模式有很多,常規的有23種,本文針對其中常用的幾種進行簡要介紹
# 設計原則在列舉具體的設計模式之前,我們要先知道設計模式本身的規范是什么,這就是設計原則,主要以下三種:- 單一職責原則(SRP):一個對象或方法只做一件事情。如果一個方法承擔了過多的職責,那么在需求的變遷過程中,需要改寫這個方法的可能性就越大。應該把對象或方法劃分成較小的粒度
- 最少知識原則(LKP):一個軟件實體應當 盡可能少地與其他實體發生相互作用,應當盡量減少對象之間的交互。如果兩個對象之間不必彼此直接通信,那么這兩個對象就不要發生直接的 相互聯系,可以轉交給第三方進行處理
- 開放-封閉原則(OCP):軟件實體(類、模塊、函數)等應該是可以 擴展的,但是不可修改,當需要改變一個程序的功能或者給這個程序增加新功能的時候,可以使用增加代碼的方式,盡量避免改動程序的源代碼,防止影響原系統的穩定


# 從最簡單的單體/單例模式開始


### 定義:
單體:一個用來劃分命名空間并將一批相關的屬性和方法組織在一起的對象
單例:顧名僅可以可以被實例化一次:在它的核心結構中只包含一個被稱為單例的特殊類。通過單例模式可以保證系統中,應用該模式的一個類只有一個實例。即一個類只有一個對象實例*在java中單例的定義:一個類有且僅有一個實例,并且自行實例化向整個系統提供*
### 優點:- 單例模式會阻止其他對象實例化其自己的單例對象的副本,從而確保所有對象都訪問唯一實例- 因為類控制了實例化過程,所以類可以靈活更改實例化過程- 單體可以控制局部變量污染
### 應用場景:- 可以用單例來劃分命名空間: 一些對象我們往往只需要一個,如某些數據的緩存- 借助單例模式,可以把代碼組織的更為一致
#### 最基本的單體模式直接導出一個方法屬性集合的對象```js// commonjs 導出module.exports = {getSingleton() {return this }}```#### 用閉包來實現單例```js

const Ins1 = (function() { let instance = null // 利用閉包特性保證實例私有化 return function(opt) { if (instance === null) { instance = this } for(let k in opt) { instance[k] = opt[k] } return instance } })()復制代碼```
測試:```jsconst i1 = new Ins1({ name: 'i1' })
const i2 = new Ins1({ name: 'i2' })
console.log(i1 === i2) // true
console.log(i1.name) // i2```補充:在node中一個文件就是一個獨立模塊,若在某個js文件中導出一個類: `class T {} export default new T` 之后在其他任何外部文件多次引入其實都是保證了 T 類只被實例化了一次而不會被多次初始化。這是因為node遵循了commonjs的規范,所有文件模塊在被引用時都會先去模塊系統的緩存中查看這個文件是否存在,如果存在就返回緩存否則才會重新創建一個模塊,而這個緩存其實也就限制了模塊內腳本的多次初始化

# 策略模式


### 定義:就是解耦,何為策略解耦: 指的是定義一些列的算法,把他們一個個封裝起來,目的就是將算法的使用與算法的實現分離開來。說白了就是以前要很多判斷的寫法,現在把判斷里面的內容抽離開來,變成一個個小的個體。如大量的if else或者switch case判斷當需求更改時需要添加和更改判斷,這違背了設計模式的對修改關閉,對擴展開放的原則
### 優點:- 減少`command c & command v`, 提高復用性- 遵循開閉原則,算法獨立易于切換、理解、拓展
### 應用場景:針對代碼多種行為設置大量的條件判斷時將每一個行為劃分為多個獨立的對象。每一個對象被稱為一個策略。設置多個這種策略對象,可以改進我們的代碼質量,也更好的進行單元測試
#### 最簡單的執行```jsfunction closure() {// 定義const strategies = {plus10: function(arg) {return arg + 10},plus100: function(arg) {return arg + 100}}// 執行return function(plus, base){return strategies[plus](base);}}const strategy = closure()
console.log(strategy('plus10', 1)) // 11console.log(strategy('plus100', 1)) // 101```#### 對比分析eg.: 代碼情景為超市促銷,vip為5折,老客戶3折,普通顧客沒折,計算最后需要支付的金額
意大利邏輯:
```jsfunction context (name, type, price) {if (type === 'vip') {return price * 0.5} else if (type === 'vip') {return price * 0.8} else {return price}}```如果type類型非常多,內部邏輯分別也不只是簡單的return一個val,那對后續的維護和測試就是災難,下面拆分邏輯為獨立單元:``` jsclass Vip {constructor () {this.discount = 0.5}getPrice (price) {return this.discount * price}}
class Old {constructor () {this.discount = 0.8}getPrice (price) {return this.discount * price}}
class Others {constructor () {}getPrice (price) {return price}}
class Context {constructor () {this.name = ''this.strategy = nullthis.price = 0}setPrice (name, strategy, price) {this.name = namethis.strategy = strategythis.price = price}getPrice () {console.log(this.name, this.strategy.getPrice(this.price), '元')return this.strategy.getPrice(this.price)}}```測試:```jsconst seller = new Contextconst vip = new Vipconst old = new Oldconst other = new Othersseller.setPrice('zs', vip, 1000)seller.getPrice()seller.setPrice('ls', old, 1000)seller.getPrice()seller.setPrice('ww', other, 1000)seller.getPrice()// output:// zs 500 元// ls 800 元// ww 1000 元```顯然邏輯多而復雜時可以極大提高代碼可讀性以及減少維護成本

# 代理模式


### 定義:為其他對象提供一種代理以控制對這個對象的訪問。在某些情況下,一個對象不適合或者不能直接引用另一個對象,而代理對象可以在客戶端和目標對象之間起到中介的作用著名的代理模式例子為引用計數(reference counting)指針對象另外代理模式還可分為:- 虛擬代理:把一些開銷很大的對象,延遲到真正需要它的時候才去創建,當對象在創建前或創建中時,由虛擬代理來扮演對象的替身;對象創建后,代理就會將請求直接委托給對象- 保護代理:用于控制不同權限的對象對目標對象的訪問- 緩存代理: 緩存代理可以作為一些開銷大的運算結果提供暫時的存儲,下次運算時,如果傳遞進來的參數跟之前一致,則可以直接返回前面存儲的運算結果
### 優點:獨立職責歸屬,便于維護測試
### 應用場景:比如圖片的懶加載,數據緩存等
#### 虛擬代理實現圖片懶加載```jsconst imgSet = (() => {let node = new Imagedocument.body.append(node)
return function(src) {node.src = src}})()
const proxyImg = (() => {let _img = new Image
_img.onload = function() {setTimeout(imgSet, 2000, this.src)}
return function(src) {imgSet('https://yphoto.eryufm.cn/upload/assets/jump.gif')_img.src = src}})()// callproxyImg(`https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1551174639&di=90b4757f68c9480f78c132c930c1df10&src=http://desk.fd.zol-img.com.cn/t_s960x600c5/g5/M00/02/02/ChMkJ1bKxkmIObywAArTTfACinwAALHjACDZuIACtNl408.jpg`)```
#### 保護代理對象a需要給對象c發送信息,為了保證a對c是不可見,可用對象b代理轉發```js// filter some no use or unneed requestions or data// A ---> B(proxy) ----> Cconst a = {name: 'a',send (target, info) {target.receive(info)}}const c = {name: 'c',receive (target, info) {console.log('c receive ', info, ' from ', target.name)}}const b = {name: 'b',receive (info) {if (info) {c.receive(this, info)}}}a.send(b, 'good morning')a.send(b, '')a.send(b, 'send again')
// output:// c receive good morning from b// c receive send again from b```上面表示一個最簡單的保護代理
#### 緩存代理顧名思義就是緩存相關的代理
有一個二級別聯動的標簽列表,第二級的各有自己所屬的多個標簽根據第一級的參數來發送指定請求來獲取,如果想要達到點擊第一級列表迅速展示出相關的第二級標簽,我們可以在系統空閑時預先將所有標簽全部獲取并緩存```js// 存儲所有標簽let tags
const sendApiGetTags = index => {// ajax.get('/api', { index })}let proxyCache = (async () => {const allTagsCache = {}const number = 5const all = []const params = {}
for (let index = 0; index < number; index++) {all.push(sendApiGetTags({...params,index}))}const list = await Promise.all(all)
list.forEach((res, i) => allTagsCache[i] = res)
return allTagsCache})()
let setTags = async index => {// 緩存中有直接拿if (proxyCache[index]) {tags = proxyCache[index]} else {// 緩存中沒有則重發請求tags = await sendApiGetTags(index)}}```


# 發布訂閱模式


### 定義:一種一對多的依賴關系,讓多個訂閱者對象同時監聽某一個主題對象。這個主題對象在自身狀態變化時,會通知所有訂閱者對象,使它們能夠自動更新自己的狀態。至于發布訂閱模式和觀察者模式是不是同一樣東西不同的人各有看法
### 優點:訂閱者可以根據自己需求當某種Action被觸發時完成自己的調度
### 應用場景:AngularJs的廣播、vue的eventbus等
#### 根據主體構建發布訂閱的基類構造發布者基類```jsclass Publisher {constructor () {// 訂閱發布者的隊列 存儲每個訂閱者this.subscribers = []}deliver (data) {// 發布消息 調用訂閱者的回調 告知訂閱者this.subscribers.forEach(fn => fn.shot(data))
return this}}```構造訂閱者基類```jsclass Observer {constructor (call) {// 傳入訂閱回調this.shot = call}subscribe (publisher) {if (!publisher.subscribers.some(v => v.shot === this.shot)) {console.log('訂閱該消息')// 判斷當前訂閱者是否訂閱publisher.subscribers.push(this)}return this}unsubscribe (publisher) {// 移除當前訂閱者console.log('取消訂閱')
publisher.subscribers = publisher.subscribers.filter(v => v.shot !== this.shot)
return this}}```測試:```jsconst pub = new Publisherconst pub2 = new Publisherconst obs = new Observer(deliver => console.log(deliver))
obs.subscribe(pub) // 訂閱該消息obs.subscribe(pub2) // 訂閱該消息
pub.deliver('pub deliver first message') // pub deliver first messagepub2.deliver('pub2 deliver first message') // pub2 deliver first message
obs.unsubscribe(pub) // 取消訂閱pub.deliver('pub deliver second message') //```

# 裝飾者模式


### 定義:裝飾模式指的是在不必改變原類文件和使用繼承的情況下,動態地擴展一個對象的功能
### 優點:- 裝飾對象和真實對象有相同的接口。這樣客戶端對象就能以和真實對象相同的方式和裝飾對象交互- 裝飾對象可以在轉發這些請求以前或以后增加一些附加功能。這樣就確保了在運行時,不用修改給定對象的結構就可以在外部增加附加的功能。在面向對象的設計中,通常是通過繼承來實現對給定類的功能擴展
### 應用場景:- 需要擴展一個類的功能,或給一個類添加附加職責- 需要動態的給一個對象添加功能,這些功能可以再動態的撤銷- 不必改動原本的邏輯造成不可知問題
#### 給所有的函數調用添加調用前和調用后的鉤子
普通函數:```jsfunction fn(msg) {console.log(msg, ' right now')}
fn('let go') // lets go right now```我們知道JS中所有的函數都是基于父類 `Function` 生成的,所以會繼承父類原型的方法,下面我們將函數的鉤子掛在父類的原型上即可:```js// 執行前Function.prototype.before = function (call) {const fn = this // 返回體本身也是函數所以支持繼續調用鉤子return function () {// 調用鉤子,同時參數傳遞到鉤子內call.apply(this, arguments)// 調用自身return fn.apply(this, arguments)}}// 執行后// 和 before 同理Function.prototype.after = function (call) {const fn = this
return function () {const res = fn.apply(this, arguments)
call.apply(this, arguments)
// 返回自身的返回值return res}}```測試:```js// 重新包裝 fnfunction fn(msg) {console.log(msg, ' right now')}
const decoratorFn = fn.before(function (msg) {console.log('when we go,', msg)}).after(function (msg){console.log('had to go', msg)})
decoratorFn('lets go')
// out put:// when we go, right now// lets go, right now// had to go, right now```

# 職責鏈(責任鏈)模式


### 定義:它是一種鏈式結構,每個節點都有可能兩種操作,要么處理該請求停止該請求操作,要么把請求轉發到下一個節點,讓下一個節點來處理請求
### 優點:職責鏈上的處理者負責處理請求,客戶只需要將請求發送到職責鏈上即可,無須關心請求的處理細節和請求的傳遞,所以職責鏈將請求的發送者和請求的處理者解耦了
### 應用場景:JS 中的事件冒泡(事件委托)就是經典案例
#### 實例分析
部門采購物品不同金額需要走不同職位的流程審批,采購部經理可自主決定1w以內的采購,總經理可以決定10w以內的采購,董事長決定100w以內的采購下面分別抽象處理者構造基類
責任鏈調度中心:```jsclass Handler {constructor() {this.next = null}setNext(_handler) {this.next = _handler}handleRequest(money) {}}

```
采購部經理:```jsclass CGBHandler extends Handler {handleRequest(money) {// 1wif (money < 10000){console.log('1w以內,同意')} else {console.log('金額太大,只能處理1w以內的采購')if (this.next) {this.next.handleRequest(money)}}}}```
總經理:```jsclass ZJLHandler extends Handler {handleRequest(money) {// 10wif (money < 100000){console.log('10w以內,同意')} else {console.log('金額太大,只能處理10w以內的采購')if (this.next) {this.next.handleRequest(money)}}}}```
董事長:```jsclass DSZHandler extends Handler {handleRequest(money) {// 100wif (money >= 100000){console.log('10萬以上的我來處理')//處理其他邏輯} }}```
封裝客戶端接口:```jsconst dispatch = (function client() {const cgb = new CGBHandler()const zjl = new ZJLHandler()const dsz = new DSZHandler()
cgb.setNext(zjl)zjl.setNext(dsz)
return cgb.handleRequest.bind(cgb)})()```測試:```js
dispath(800000)// output:// 金額太大,只能處理1w以內的采購// 金額太大,只能處理10w以內的采購// 10萬以上的我來處理
dispath(7000)// output:// 1w以內,同意
```補充:- 純的責任鏈:要求請求在這些對象鏈中必須被處理,而且一個節點處理對象,要么只處理請求,要么把請求轉發給下個節點對象處理
- 不純的責任鏈:要求在責任鏈里不一定會有處理結構,而且一個節點對象,即可以處理部分請求,并把請求再轉發下個節點處理
......未完待續...?


原文鏈接:

https://rollawaypoint.github.io/2019/02/24/do%20something/JS%E5%B8%B8%E7%94%A8%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/



轉載于:https://juejin.im/post/5c767d27518825763c6d8f96

總結

以上是生活随笔為你收集整理的JS常用的设计模式的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。