JavaScript 設計模式:深入了解有效的設計

JavaScript 設計模式:深入了解有效的設計

今天,我們將戴上計算機科學的帽子,學習一些常見的設計模式。設計模式為開發人員提供了以可重用且優雅的方式解決技術問題的方法。有興趣成為一名更好的 JavaScript 開發人員嗎?然后繼續閱讀。

重新發布的教程

每隔幾周,我們就會重新訪問網站歷史上一些讀者最喜歡的帖子。本教程首次發布于 2012 年 7 月。


簡介

可靠的設計模式是可維護的軟件應用程序的基本構建塊。如果您曾經參加過技術面試,您一定會喜歡被問到這些問題。在本教程中,我們將介紹一些您今天就可以開始使用的模式。

立即學習Java免費學習筆記(深入)”;


什么是設計模式?

設計模式是可重用的軟件解決方案

簡單地說,設計模式是針對軟件開發過程中經常出現的特定類型問題的可重用的軟件解決方案。經過多年的軟件開發實踐,專家們已經找到了解決類似問題的方法。這些解決方案已被封裝到設計模式中。所以:

  • 模式是經過驗證的軟件開發問題解決方案
  • 模式是可擴展的,因為它們通常是結構化的并且具有您應該遵循的規則
  • 模式可以重復使用來解決類似的問題

我們將在本教程中進一步介紹一些設計模式的示例。


設計模式的類型

在軟件開發中,設計模式通常分為幾類。我們將在本教程中介紹三個最重要的內容。下面簡要解釋一下:

  1. 創建模式側重于創建對象或類的方法。這聽起來可能很簡單(在某些情況下確實如此),但大型應用程序需要控制對象創建過程。

  2. 結構設計模式側重于管理對象之間關系的方法,以便以可擴展的方式構建應用程序。結構模式的一個關鍵方面是確保應用程序某一部分的更改不會影響所有其他部分。

  3. 行為模式側重于對象之間的通信。

閱讀這些簡短說明后您可能仍有疑問。這是很自然的,一旦我們深入研究下面的一些設計模式,事情就會變得清晰起來。所以請繼續閱讀!


關于 JavaScript 中的類的注釋

在閱讀設計模式時,您經常會看到對類和對象的引用。這可能會令人困惑,因為 JavaScript 并不真正具有“類”的構造;更正確的術語是“數據類型”。

JavaScript 中的數據類型

JavaScript 是一種面向對象的語言,其中對象以原型繼承的概念從其他對象繼承。可以通過定義所謂的構造函數來創建數據類型,如下所示:

function Person(config) {     this.name = config.name;     this.age = config.age; }  Person.prototype.getAge = function() {     return this.age; };  var tilo = new Person({name:"Tilo", age:23 }); console.log(tilo.getAge()); 

請注意在 Person 數據類型上定義方法時使用 prototype。由于多個 Person 對象將引用相同的原型,因此這允許 getAge() 方法由 Person 數據類型的所有實例共享,而不是為每個實例重新定義它。此外,任何繼承自 Person 的數據類型都可以訪問 getAge() 方法。

處理隱私

JavaScript 中的另一個常見問題是不存在真正意義上的私有變量。然而,我們可以使用閉包來模擬隱私。考慮以下代碼片段:

var retinaMacbook = (function() {      //Private variables     var RAM, addRAM;      RAM = 4;      //Private method     addRAM = function (additionalRAM) {         RAM += additionalRAM;     };      return {          //Public variables and methods         USB: undefined,         insertUSB: function (device) {             this.USB = device;         },          removeUSB: function () {             var device = this.USB;             this.USB = undefined;             return device;         }     }; })(); 

在上面的示例中,我們創建了一個 retinaMacbook 對象,具有公共和私有變量和方法。這就是我們使用它的方式:

retinaMacbook.insertUSB("myUSB"); console.log(retinaMacbook.USB); //logs out "myUSB" console.log(retinaMacbook.RAM) //logs out undefined 

我們可以使用 JavaScript 中的函數和閉包做更多事情,但我們不會在本教程中詳細介紹所有內容。有了關于 JavaScript 數據類型和隱私的這一小課,我們就可以繼續學習設計模式。


創意設計模式

有許多不同類型的創作設計模式,但我們將在本教程中介紹其中兩種:構建器和原型。我發現這些內容的使用頻率足以引起人們的關注。

構建器模式

構建器模式經常在 Web 開發中使用,您之前可能已經使用過它,但沒有意識到。簡單來說,這個模式可以定義如下:

應用構建器模式允許我們僅通過指定對象的類型和內容來構造對象。我們不必顯式創建該對象。

例如,您可能已經在 jquery 中執行過無數次了:

var myDiv = $('<div id="myDiv">This is a div.</div>');  //myDiv now represents a jQuery Object referencing a dom node.  var someText = $('<p></p>'); //someText is a jQuery object referencing an HTMLParagraphElement  var input = $('<input>'); 

看一下上面的三個例子。在第一個中,我們傳入了包含一些內容的

元素。在第二個中,我們傳入了一個空的

標簽。在最后一個中,我們傳入了 元素。這三個的結果都是相同的:我們返回了一個引用 DOM 節點的 jQuery 對象。

$變量采用了jQuery中的Builder模式。在每個示例中,我們都返回了一個 jQuery DOM 對象,并且可以訪問 jQuery 庫提供的所有方法,但我們從未顯式調用 document.createElement。 JS 庫在幕后處理了所有這些事情。

想象一下,如果我們必須顯式創建 DOM 元素并向其中插入內容,那將需要耗費多少工作!通過利用構建器模式,我們能夠專注于對象的類型和內容,而不是顯式創建它。

原型模式

前面,我們介紹了如何通過函數在 JavaScript 中定義數據類型,以及如何向對象的 prototype 添加方法。原型模式允許對象通過原型從其他對象繼承。

原型模式是通過克隆基于現有對象的模板來創建對象的模式。

這是在 JavaScript 中實現繼承的一種簡單而自然的方式。例如:

var Person = {     numFeet: 2,     numHeads: 1,     numHands:2 };  //Object.create takes its first argument and applies it to the prototype of your new object. var tilo = Object.create(Person);  console.log(tilo.numHeads); //outputs 1 tilo.numHeads = 2; console.log(tilo.numHeads) //outputs 2 

Person 對象中的屬性(和方法)應用于 tilo 對象的原型。如果我們希望它們不同,我們可以重新定義 tilo 對象的屬性。

在上面的例子中,我們使用了 Object.create()。但是,Internet Explorer 8 不支持較新的方法。在這些情況下,我們可以模擬它的行為:

var vehiclePrototype = {    init: function (carModel) {     this.model = carModel;   },    getModel: function () {     console.log( "The model of this vehicle is " + this.model);   } };   function vehicle (model) {    function F() {};   F.prototype = vehiclePrototype;    var f = new F();    f.init(model);   return f;  }  var car = vehicle("Ford Escort"); car.getModel(); 

此方法唯一的缺點是不能指定只讀屬性,而可以在使用 Object.create() 時指定。盡管如此,原型模式展示了對象如何從其他對象繼承。


結構設計模式

在弄清楚系統應該如何工作時,結構設計模式非常有用。它們使我們的應用程序能夠輕松擴展并保持可維護性。我們將研究本組中的以下模式:復合模式和外觀模式。

復合模式

復合模式是您之前可能使用過但沒有意識到的另一種模式。

復合模式表示可以像對待組中的單個對象一樣對待一組對象。

那么這是什么意思呢?好吧,考慮一下 jQuery 中的這個示例(大多數 JS 庫都有與此等效的示例):

$('.myList').addClass('selected'); $('#myItem').addClass('selected');  //dont do this on large tables, it's just an example. $("#dataTable tbody tr").on("click", function(event){     alert($(this).text()); });  $('#myButton').on("click", function(event) {     alert("Clicked."); }); 

無論我們處理的是單個 DOM 元素還是 DOM 元素數組,大多數 JavaScript 庫都提供一致的 API。在第一個示例中,我們可以將 selected 類添加到 .myList 選擇器選取的所有項目中,但在處理單個 DOM 元素 #myItem 時,我們可以使用相同的方法。同樣,我們可以使用 on() 方法在多個節點上附加事件處理程序,或者通過相同的 API 在單個節點上附加事件處理程序。

通過利用復合模式,jQuery(和許多其他庫)為我們提供了一個簡化的 API。

復合模式有時也會引起問題。在 JavaScript 等松散類型語言中,了解我們正在處理單個元素還是多個元素通常會很有幫助。由于復合模式對兩者使用相同的 API,因此我們經常會誤認為其中一個,并最終出現意想不到的錯誤。某些庫(例如 YUI3)提供兩種單獨的獲取元素的方法(Y.one() 與 Y.all())。

外觀模式

這是我們認為理所當然的另一個常見模式。事實上,這是我最喜歡的之一,因為它很簡單,而且我已經看到它被到處使用來幫助解決瀏覽器不一致的問題。以下是外觀模式的含義:

外觀模式為用戶提供了一個簡單的界面,同時隱藏了其底層的復雜性。

外觀模式幾乎總能提高軟件的可用性。再次以 jQuery 為例,該庫中比較流行的方法之一是 ready() 方法:

$(document).ready(function() {      //all your code goes here...  }); 

ready() 方法實際上實現了一個門面。如果您查看源代碼,您會發現以下內容:

ready: (function() {      ...      //Mozilla, Opera, and Webkit     if (document.addEventListener) {         document.addEventListener("DOMContentLoaded", idempotent_fn, false);         ...     }     //IE event model     else if (document.attachEvent) {          // ensure firing before onload; maybe late but safe also for iframes         document.attachEvent("onreadystatechange", idempotent_fn);          // A fallback to window.onload, that will always work         window.attachEvent("onload", idempotent_fn);          ...          }  }) 

在底層, ready() 方法并不那么簡單。 jQuery 規范了瀏覽器的不一致,以確保在適當的時間觸發 ready()。但是,作為開發人員,您會看到一個簡單的界面。

大多數外觀模式示例都遵循這一原則。在實現時,我們通常依賴于底層的條件語句,但將其作為一個簡單的界面呈現給用戶。實現此模式的其他方法包括 animate() 和 css()。你能想到為什么這些會使用外觀模式嗎?


行為設計模式

任何面向對象的軟件系統都會在對象之間進行通信。不組織這種溝通可能會導致難以發現和修復的錯誤。行為設計模式規定了組織對象之間通信的不同方法。在本節中,我們將研究觀察者模式和中介者模式。

觀察者模式

觀察者模式是我們將要經歷的兩種行為模式中的第一種。它是這樣說的:

在觀察者模式中,主題可以擁有對其生命周期感興趣的觀察者列表。每當主題做了一些有趣的事情時,它都會向其觀察者發送通知。如果觀察者不再有興趣聽主題,則主題可以將其從列表中刪除。

聽起來很簡單,對吧?我們需要三種方法來描述這種模式:

  • publish(data):當主題有通知要發出時調用。某些數據可以通過此方法傳遞。
  • subscribe(observer):由主題調用以將觀察者添加到其觀察者列表中。
  • unsubscribe(observer):由主題調用,從其觀察者列表中刪除觀察者。

事實證明,大多數現代 JavaScript 庫都支持這三種方法作為其自定義事件基礎結構的一部分。通常,有一個 on() 或 attach() 方法,一個 trigger() 或 fire() 方法,以及一個 off() 或 detach()方法。考慮以下代碼片段:

//We just create an association between the jQuery events methods 
//and those prescribed by the Observer Pattern but you don't have to. var o = $( {} ); $.subscribe = o.on.bind(o); $.unsubscribe = o.off.bind(o); $.publish = o.trigger.bind(o);  // Usage document.on( 'tweetsReceived', function(tweets) {     //perform some actions, then fire an event      $.publish('tweetsShow', tweets); });  //We can subscribe to this event and then fire our own event. $.subscribe( 'tweetsShow', function() {     //display the tweets somehow     ..      //publish an action after they are shown.     $.publish('tweetsDisplayed); });  $.subscribe('tweetsDisplayed, function() {     ... }); 

觀察者模式是實現起來比較簡單的模式之一,但它非常強大。 JavaScript 非常適合采用這種模式,因為它本質上是基于事件的。下次開發 Web 應用程序時,請考慮開發彼此松散耦合的模塊,并采用觀察者模式作為通信方式。如果涉及太多主體和觀察者,觀察者模式可能會出現問題。這可能會發生在大型系統中,我們研究的下一個模式將嘗試解決這個問題。

調解者模式

我們要討論的最后一個模式是中介者模式。它與觀察者模式類似,但有一些顯著的差異。

中介者模式提倡使用單個共享主題來處理與多個對象的通信。所有對象都通過中介者相互通信。

現實世界中一個很好的類比是空中交通塔,它負責處理機場和航班之間的通信。在軟件開發領域,當系統變得過于復雜時,通常會使用中介者模式。通過放置中介,可以通過單個對象來處理通信,而不是讓多個對象相互通信。從這個意義上說,中介者模式可以用來替代實現觀察者模式的系統。

在這個要點中,Addy Osmani 提供了中介者模式的簡化實現。讓我們談談如何使用它。想象一下,您有一個 Web 應用程序,允許用戶單擊專輯并播放其中的音樂。您可以像這樣設置中介者:

$('#album').on('click', function(e) {     e.preventDefault();     var albumId = $(this).id();     mediator.publish("playAlbum", albumId); });   var playAlbum = function(id) {     …     mediator.publish("albumStartedPlaying", {songList: [..], currentSong: "Without You"});  };  var logAlbumPlayed = function(id) {     //Log the album in the backend };  var updateUserInterface = function(album) {     //Update UI to reflect what's being played };  //Mediator subscriptions mediator.subscribe("playAlbum", playAlbum); mediator.subscribe("playAlbum", logAlbumPlayed); mediator.subscribe("albumStartedPlaying", updateUserInterface); 

此模式相對于觀察者模式的好處是單個對象負責通信,而在觀察者模式中,多個對象可以相互監聽和訂閱。

在觀察者模式中,沒有封裝約束的單個對象。相反,觀察者和主體必須合作來維持約束。通信模式由觀察者和主體互連的方式決定:一個主體通常有許多觀察者,有時一個主體的觀察者是另一個觀察者的主體。


結論

過去已經有人成功應用過它。

設計模式的偉大之處在于,過去已經有人成功地應用過它。有許多開源代碼可以在 JavaScript 中實現各種模式。作為開發人員,我們需要了解現有的模式以及何時應用它們。我希望本教程可以幫助您在回答這些問題上更進一步。


補充閱讀

本文的大部分內容可以在 Addy Osmani 所著的優秀的《學習 JavaScript 設計模式》一書中找到。這是一本根據知識共享許可免費發布的在線圖書。本書廣泛涵蓋了許多不同模式的理論和實現,包括普通 JavaScript 和各種 JS 庫。我鼓勵您在開始下一個項目時將其作為參考。

? 版權聲明
THE END
喜歡就支持一下吧
點贊10 分享