<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>
0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

一文詳解前端常用設計模式

OSC開源社區 ? 來源:OSC開源社區 ? 2023-11-30 10:19 ? 次閱讀

設計模式一直是程序員談論的“高端”話題之一,總有一種敬而遠之的心態。在了解后才知道在將函數作為一等對象的語言中,有許多需要利用對象多態性的設計模式,比如單例模式、 策略模式等,這些模式的結構與傳統面向對象語言的結構大相徑庭,實際上已經融入到了語言之中,我們可能經常使用它們,只是不知道它們的名字而已。

設計模式

相信了解的,都知道有 20 多種...

其中按類型分有三種。為“創建型”封裝了創建對象的變化過程,“結構型”將對象之間組合的變化封裝,“行為型”則是抽離對象的變化行為。

接下來,本文將以常用原則中從“單一功能”和“開放封閉”這兩大原則為主線,分別介紹“創建型”、“結構型”和“行為型”中最具代表性的單例、策略、代理、觀察者這幾大設計模式。

1 常用原則

這些設計原則通常指的是單一職責原則、里氏替換原則、依賴倒置原則、接口隔離原則、合成復用原則和最少知識原則。因為案例中涉及單一職責原則和開放-封閉原則,所以只介紹這兩部分。

1.1 單一職責原則(SRP)

一個對象(方法)只做一件事情

如果我們有兩個動機去改寫一個方法,那么這個方法就具有兩個職責。每個職責都是變化的一個軸線,如果一個方法承擔了過多的職責,那么在需求的變遷過程中,需要改寫這個方法的可能性就越大。

SRP 原則的優點是降低了單個類或者對象的復雜度,按照職責把對象分解成更小的粒度, 這有助于代碼的復用,也有利于進行單元測試。當一個職責需要變更的時候,不會影響到其他的職責。

但 SRP 原則也有一些缺點,最明顯的是會增加編寫代碼的復雜度。當我們按照職責把對象分解成更小的粒度之后,實際上也增大了這些對象之間相互聯系的難度。

1.2 開放-封閉原則

對象(類、模塊、函數等)應該是可擴展但不可修改的。

就算我們作為維護者,拿到的是一份混淆壓縮過的代碼也沒有關系。只要它從前是個穩定運行的函數,那么以后也不會因為我們的新增需求而產生錯誤。新增的代碼和原有的代碼可以井水不犯河水。

2 單例模式

單例模式 (Singleton Pattern)又稱為單體模式,保證一個類只有一個實例,并提供一個訪問它的全局訪問點。也就是說,第二次使用同一個類創建新對象的時候,應該得到與第一次創建的對象完全相同的對象。

2.1 舉個 登錄彈窗

我們正在開發一個網站,網站類型是一個視頻網站,網站有個登錄按鈕,點擊登錄會彈出一個登錄框進行登錄,你現在可能已經聯想到,這個登錄框一定是頁面唯一的一個 dom 節點,一個頁面存在兩個登錄框是不存在的!

如果要實現這種效果第一種解決方案就是在頁面加載的時候就已經創建好 dom 節點,并且設置樣式為 display 為 none,當點擊登錄時修改為 block 顯示。

beba8f34-8ea8-11ee-939d-92fbcf53809c.png

這種方式有一個問題,也許我們進入當前網站只是玩玩游戲或者看看天氣,根本不需要進行登錄操作,因為登錄浮窗總是一開始就被創建好,那么很有可能將白白浪費一些 DOM 節點。所以開始改進,當我們每次點擊登錄按鈕的時候,再創建一個新的登錄浮窗 div。

bed609b2-8ea8-11ee-939d-92fbcf53809c.png

雖然我們可以在點擊浮窗上的關閉按鈕時(此處未實現)把這個浮窗從頁面中刪除掉,但這樣頻繁地創建和刪除節點明顯是不合理的,也是不必要的。所以我們再次進行改進,用一個變量來判斷是否已經創建過登錄浮窗。

bee9f9ae-8ea8-11ee-939d-92fbcf53809c.png

這段代碼仍然是違反單一職責原則(就一個類(通常也包括對象和函數等)而言,應該僅有一個引起它變化的原因)的,創建對象和管理單例的邏輯都放在 createLoginLayer 對象內部。所以將管理單例的邏輯單獨提出來。

bef5f9f2-8ea8-11ee-939d-92fbcf53809c.png

用于創建登錄浮窗的方法用參數 fn 的形式傳入 getSingle,我們不僅可以傳入 createLoginLayer,還能傳入 createScript、createIframe、createXhr 等。之后再讓 getSingle 返回 一個新的函數,并且用一個變量 result 來保存 fn 的計算結果。result 變量因為身在閉包中,它永遠不會被銷毀。在將來的請求中,如果 result 已經被賦值,那么它將返回這個值。

創建實例對象的職責和管理單例的職責分別放置在兩個方法里,這兩個方法可以獨立變化而互不影響。

所以,在適合的時候才創建對象,并且只創建唯一的一個,如果創建對象和管理創建單例職責分布在兩個不同的方法當中,解耦性的加持會讓這個模式威力大大增加,這是能提高性能的一個突破口。

2.2 其他場景

使用場景:Redux、Vuex 等狀態管理工具,還有我們常用的 window 對象、全局緩存等。

多次引用只會使用一個庫引用,如 jQuery,lodash,moment 等。

Vuex / Redux。Vuex 和 Redux 數據保存在單一 store 中,Mobx 將數據保存在分散的多個 store 中。

2.3 小結

在 getSinge 函數中,實際上也提到了閉包和高階函數的概念。單例模式是一種簡單但非常實用的模式,考慮在合適的時候才創建對象,并且只創建唯一的一個。創建實例對象的職責和管理單例的職責分別放置在兩個方法里,這兩個方法可以獨立變化而互不影響。

3 代理模式

代理模式的定義:代理模式給某一個對象提供一個代理對象,并由代理對象控制對原對象的引用。通俗的來講代理模式就是我們生活中常見的中介。

為什么要使用代理模式

中介隔離作用:在某些情況下,一個客戶類不想或者不能直接引用一個委托對象,而代理類對象可以在客戶類和委托對象之間起到中介的作用,其特征是代理類和委托類實現相同的接口。

開閉原則,增加功能:代理類除了是客戶類和委托類的中介之外,我們還可以通過給代理類增加額外的功能來擴展委托類的功能,這樣做我們只需要修改代理類而不需要再修改委托類,符合代碼設計的開閉原則。

3.1 舉個 圖片預加載

圖片預加載是一種常用的技術,如果直接給某個 img 標簽節點設置 src 屬性, 由于圖片過大或者網絡不佳,圖片的位置往往有段時間會是一片空白。常見的做法是先用一張 loading 圖片占位,然后用異步的方式加載圖片,等圖片加載好了再把它填充到 img 節點里,這種場景就很適合使用虛擬代理。

bf06a89c-8ea8-11ee-939d-92fbcf53809c.png

但這里可以看到 MyImage 這個對象承擔了多項職責,就意味著這個對象將變得巨大,引起它變化的原因可能會有 2 個。當系統需求發生改變時,盡量不修改系統原有代碼功能,應該擴展模塊的功能,來實現新的需求。

所以 5 年后的網速快到根本不再需要預加載,我們可能希望把預加載圖片的這段代碼從 MyImage 對象里刪掉。這時候就不得不改動 MyImage 對象了。所以對于上述代碼進行優化。

bf1f0b4e-8ea8-11ee-939d-92fbcf53809c.png

這里通過 proxyImage 間接地訪問 MyImage。proxyImage 控制了客戶對 MyImage 的訪問,并且在此過程中加入一些額外的操作,比如在真正的圖片加載好之前,先把 img 節點的 src 設置為 一張本地的 loading 圖片,避免了在圖片被加載好之前,頁面中有一段長長的空白時間。

實際上,我們需要的只是給 img 節點設置 src,預加載圖片只是一個錦上添花的功能。如果 能把這個操作放在另一個對象里面,自然是一個非常好的方法。于是代理的作用在這里就體現出 來了,代理負責預加載圖片,預加載的操作完成之后,把請求重新交給本體 MyImage。既滿足了單一職責原則,又滿足了開放封閉原則。

3.2 再舉個 合并請求

在 Web 開發中,也許最大的開銷就是網絡請求。假設我們在做一個文件同步的功能,當我們選中一個 checkbox 的時候,它對應的文件就會被同步到另外一臺備用服務器上面,如圖所示。

bf3664a6-8ea8-11ee-939d-92fbcf53809c.png

我們先在頁面中放置好這些 checkbox 節點,接下來,給這些 checkbox 綁定點擊事件,并且在點擊的同時往另一臺服務器同步文件。

bf3d3024-8ea8-11ee-939d-92fbcf53809c.png

當我們選中 3 個 checkbox 的時候,依次往服務器發送了 3 次同步文件的請求。而點擊一個 checkbox 并不是很復雜的操作,但如果有 100 個文件,1w 個文件,可以預見,如此頻繁的網絡請求將會帶來相當大的開銷。

解決方案是,我們可以通過一個代理函數 proxySynchronousFile 來收集一段時間之內的請求,最后一次性發送給服務器。比如我們等待 2 秒之后才把這 2 秒之內需要同步的文件 ID 打包發給服務器,如果不是對實時性要求非常高的系統,2 秒的延遲不會帶來太大副作用,卻能大大減輕服務器的壓力。

bf47d89e-8ea8-11ee-939d-92fbcf53809c.png

3.3 小結

縱觀圖片預加載整個程序,我們并沒有改變或者增加 MyImage 的接口,但是通過代理對象,實際上給系統添加了新的行為。這是符合開放—封閉原則的。給 img 節點設置 src 和圖片預加載這兩個功能,被隔離在兩個對象里,它們可以各自變化而不影響對方。何況就算有一天我們不再需要預加載,那么只需要改成請求本體而不是請求代理對象即可。

代理類主要負責為委托類預處理消息、過濾消息、把消息轉發給委托類,以及事后對返回結果的處理等。代理類本身并不真正實現服務,而是同過調用委托類的相關方法,來提供特定的服務。真正的業務功能還是由委托類來實現,但是可以在業務功能執行的前后加入一些公共的服務。例如我們想給項目加入緩存、日志這些功能,我們就可以使用代理類來完成,而沒必要打開已經封裝好的委托類。

雖然代理模式非常有用,但我們在編寫業務代碼的時候,往往不需要去預先猜測是否需要使用代理模式。當真正發現不方便直接訪問某個對象的時候,再編寫代理也不遲。

4 策略模式

該模式定義了一系列算法,并將每個算法封裝起來,使它們可以相互替換,且算法的變化不會影響使用算法的客戶。

4.1 舉個 多條件業務

多業務場景下的訂單跳轉一直是個很頭疼的問題,因為每條業務的訂單可能需要定制,跳轉的詳情也可能不太一樣,當業務線過多的時候,很容易陷入多重條件地獄,不斷去累加判斷條件。

bf552ad0-8ea8-11ee-939d-92fbcf53809c.png如上這種,雖然是看起來條件很多但是屬于單條件,我們可以使用策略模式來簡單改造,如下所示:

bf714238-8ea8-11ee-939d-92fbcf53809c.png

但是我們的業務可能會更加復雜,訂單頁面我們采用了 h5 嵌入其他應用的模式,我們可能將此業務嵌入快應用、RN、原生 App、小程序、h5 等各種環境里面,展示的內容以及路由跳轉可能都不盡相同,我們為了增加難度,更為直觀的體現,所以每種對應的規則都默認是完全不同的方法。

bf827a12-8ea8-11ee-939d-92fbcf53809c.png

看到上述代碼,可能人生已經絕望,因為實際中的訂單類型遠不止 3 種,環境類型也遠不止 3 種,然而還可能有更多的附件條件并沒有加上去。且越來越多的條件加入的同時,造成代碼的可讀性、可維護性、可迭代性急速下降,雖然上述代碼格式化之后,看起來倒還是很工整的。

雖然上述是多重嵌套條件,但拆分開來還是可以理解為訂單類型跟環境類型的組合,我們借助 es6 map 對象來進行改造。

bf91de58-8ea8-11ee-939d-92fbcf53809c.pngbfa098b2-8ea8-11ee-939d-92fbcf53809c.png

如上述重構后的,我們借助 map 對象的特性(此處不對 map 對象做更深的拓展講解)。如果再有新增的規則,我們可以放在 map 里面進行新增對應規則與方法,減少條件嵌套地獄出現,并且邏輯會更加清晰。但實際情況中還可以對類似的方法進行合并,邏輯會更加清晰。

4.2 再舉個 表單校驗

在一個 Web 項目中,注冊、登錄、修改用戶信息等功能的實現都離不開提交表單。

在將用戶輸入的數據交給后臺之前,常常要做一些客戶端力所能及的校驗工作,比如注冊的時候需要校驗是否填寫了用戶名,密碼的長度是否符合規定等等。這樣可以避免因為提交不合法數據而帶來的不必要網絡開銷。

假設我們正在編寫一個注冊的頁面,在點擊注冊按鈕之前,有如下幾條校驗邏輯。

用戶名不能為空。

密碼長度不能少于 6 位。

手機號碼必須符合格式。

bfb1d2da-8ea8-11ee-939d-92fbcf53809c.png

傳統編寫表單校驗

bfbee27c-8ea8-11ee-939d-92fbcf53809c.png

這是一種很常見的代碼編寫方式,它的缺點有以下。

registerForm.onsubmit 函數比較龐大,包含了很多 if-else 語句,這些語句需要覆蓋所有的校驗規則。

registerForm.onsubmit 函數缺乏彈性,如果增加了一種新的校驗規則,或者想把密碼的長度校驗從 6 改成 8,我們都必須深入 registerForm.onsubmit 函數的內部實現,這是違反開放—封閉原則的。

算法的復用性差,如果在程序中增加了另外一個表單,這個表單也需要進行一些類似的校驗,那我們很可能將這些校驗邏輯復制得漫天遍野。

下面我們將用策略模式來重構表單校驗的代碼,很顯然第一步我們要把這些校驗邏輯都封裝成策略對象。

bfca0a3a-8ea8-11ee-939d-92fbcf53809c.png

4.3 小結

通過使用策略模式重構代碼,我們消除了原程序中大片的條件分支語句,代碼變得更加清晰,各個類的職責更加鮮明。一般策略對象往往被函數所代替,這時策略模式就成為一種“隱形”的模式。把這些校驗邏輯都封裝成策略對象,也可以復用在系統的其他地方,從而避免許多重復的復制粘貼工作。

5 觀察者模式

觀察者模式建立了一套觸發機制,幫助我們完成更松耦合的代碼編寫。

它定義對象間的一種一對多的依賴關系,當一個對象的狀態發生改變時,所有依賴于它的對象都將得到通知。

5.1 舉個 網站登錄

假如我們正在開發一個商城網站,網站里有 header 頭部、nav 導航、消息列表、購物車等模塊。這幾個模塊的渲染有一個共同的前提條件,就是必須先用 ajax 異步請求獲取用戶的登錄信息。這是很正常的,比如用戶的名字和頭像要顯示在 header 模塊里,而這兩個字段都來自用戶登錄后返回的信息。

bfe61590-8ea8-11ee-939d-92fbcf53809c.png

現在登錄模塊是我們負責編寫的,但我們還必須了解 header 模塊里設置頭像的方法叫 setAvatar、購物車模塊里刷新的方法叫 refresh,這種耦合性會使程序變得僵硬,header 模塊不能隨意再改變 setAvatar 的方法名,它自身的名字也不能被改為 header1、header2。

等到有一天,項目中又新增了一個收貨地址管理的模塊,這個模塊本來是另一個同事所寫的, 而此時你正在度假,但是他卻不得不給你打電話:“Hi,登錄之后麻煩刷新一下收貨地址列表?!庇谑悄阌址_你 3 個月前寫的登錄模塊,在最后部分加上這行代碼。

bfec2b9c-8ea8-11ee-939d-92fbcf53809c.png

用觀察者模式重寫之后,對用戶信息感興趣的業務模塊將自行訂閱登錄成功的消息事件。當登錄成功時,登錄模塊只需要發布登錄成功的消息,而業務方接受到消息之后,就會開始進行各自的業務處理,登錄模塊并不關心業務方究竟要做什么,也不想去了解它們的內部細節。

c00899bc-8ea8-11ee-939d-92fbcf53809c.png

我們隨時可以把 setAvatar 的方法名改成 setTouxiang。如果有一天在登錄完成之后,又增加一個刷新收貨地址列表的行為,那么只要在收貨地址模塊里加上監聽消息的方法即可。

5.2 小結

觀察者模式的優點非常明顯,一為時間上的解耦,二為對象之間的解耦。它的應用非常廣泛,既可以用在異步編程中,也可以幫助我們完成更松耦合的代碼編寫。但是創建訂閱者本身要消耗一定的時間和內存,而且當你訂閱一個消息后,也許此消息最后都未發生,但這個訂閱者會始終存在于內存中。另外,觀察者模式雖然可以弱化對象之間的聯系,但如果過度使用的話,對象和對象之間的必要聯系也將被深埋在背后,會導致程序難以跟蹤維護和理解。

6 總結

烹飪有菜譜,游戲有攻略,干啥都有一些能夠讓我們達到目標的“套路”,在程序世界,編程的“套路”就是設計模式。

前端常用的設計模式出從單例、代理、策略、觀察者模式入手,帶大家去了解設計模式的核心操作是去觀察你整個邏輯里面的變與不變,然后將變與不變分離,達到使變化的部分靈活、不變的地方穩定的目的。在我們遇到相似的問題、場景時,能快速找到更優的方式解決。





審核編輯:劉清

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • SRC
    SRC
    +關注

    關注

    0

    文章

    60

    瀏覽量

    17829
  • 前端設計
    +關注

    關注

    0

    文章

    18

    瀏覽量

    9998
  • DOM
    DOM
    +關注

    關注

    0

    文章

    16

    瀏覽量

    9536

原文標題:前端常用設計模式初探

文章出處:【微信號:OSC開源社區,微信公眾號:OSC開源社區】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    NE555中資料詳解

    NE555中資料詳解
    發表于 08-20 13:49

    NE555中資料詳解

    NE555中資料詳解
    發表于 08-21 09:27

    NE555中資料詳解

    NE555中資料詳解
    發表于 11-23 22:08

    FAT32件系統詳解

    FAT32件系統詳解
    發表于 08-17 12:34

    《DM368 視頻前端信號采集詳解》- 該文對理解DM8127/DM38x的并口采集也有幫助

    歡迎提出意見/建議! DM368 視頻前端信號采集詳解 http://www.ti.com.cn/cn/lit/an/zhca600/zhca600.pdf
    發表于 05-28 07:54

    開關電源常用電路詳解資料分享

    開關電源常用電路詳解資料分享【眾籌活動】每天學習1小時 張飛帶你兩個月精通半橋LLC開關電源?。ㄗ詈?天)
    發表于 02-19 22:17

    如何在兩個局域網內共享臺打印機 ,常用網絡命令及命令實例詳解

    怎樣在兩個局域網內共享臺打印機 。常用網絡命令及命令實例詳解
    發表于 06-09 16:31

    詳解ARM指令與ARM匯編

    1、2、3、ARM嵌入式開發之ARM指令與ARM匯編入門4、ARM嵌入式開發之ARM匯編高級教程與APCS規范詳解視頻下載地址:內容:01_ARM嵌入式開發之ARM基礎概念介紹...
    發表于 12-23 06:45

    詳解電池組

    電池荷電狀態和健康水平的電量計。監視器(模擬前端 (AFE))由于保護功能是所有電池組電子元件的主要功能,般的電池組器件都配備有某些級別的保護功能。例如,個電池監視器(模擬前端 [
    發表于 11-17 07:50

    看懂常用貼片電感封裝規格可以升級嗎

    看懂常用貼片電感封裝規格可以升級嗎編輯:谷景電子貼片電感作為電感產品中非常重要的個類型,它的應用普及度是非常廣泛的??梢哉f在各種大家熟悉的電子產品中都能看到貼片電感的身影。關于貼
    發表于 12-17 14:25

    音箱評測的常用術語詳解

    音箱評測的常用術語詳解
    發表于 11-22 12:27 ?1568次閱讀

    AD常用規則圖片詳解下載

    AD常用規則圖片詳解下載
    發表于 05-17 11:01 ?0次下載

    單片機常用芯片系列(二)——DS18B20詳解

    單片機常用芯片系列(二)——DS18B20詳解
    發表于 11-26 14:36 ?11次下載
    單片機<b class='flag-5'>常用</b>芯片系列(二)——DS18B20<b class='flag-5'>詳解</b>

    HS6621 串口透傳 模式 - [詳解]

    HS6621串口透傳模式詳解
    發表于 12-08 18:36 ?32次下載
    HS6621 串口透傳 <b class='flag-5'>模式</b> - [<b class='flag-5'>詳解</b>]

    直線模組常用的驅動模式有哪些?

    直線模組常用的驅動模式有哪些?
    的頭像 發表于 03-30 17:39 ?597次閱讀
    直線模組<b class='flag-5'>常用</b>的驅動<b class='flag-5'>模式</b>有哪些?
    亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
    <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
    <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
    <acronym id="s8ci2"></acronym>
    <acronym id="s8ci2"><center id="s8ci2"></center></acronym>