理解JavaScript中的設計模式

jobbole發表於2012-12-25

  簡介:可靠的設計模式是可維護軟體的基石,如果你曾參與技術面試,很有可能被問到關於設計模式的這類東西。下面這個指南中,我們將學習一些今天就可以用得著的設計模式。

  什麼是設計模式?

  簡單來說,設計模式就是對特定型別問題重用的軟體解決方案,這些問題在軟體開發的時候經常會碰到,通過很多年的實踐,專家對一些相似地問題總結出一些方法,這些方法就封裝成為一種設計模式,所以:

  模式是一種經驗證的用於解決軟體開發問題的方案。

  模式是可擴充套件的,因為他們經常被結構化而且你需要遵循某些規則。

  對於相似的問題,模式可被重用

  在接下來的教程中,我們將直接給出一些設計模式的例子。

  設計模式的種類

  軟體開發中,設計模式通常分為幾種類別,在這篇教程中我們重點介紹以下三種:

  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物件將引用同一個prototype,這樣就允許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的資料型別和私有變數。現在我們可以學習設計模式了。

  建立型設計模式:

  有很多種不同的建立設計模式,但是在這裡我們主要討論兩種,建造模式(Builder)和原型模式(Prototype)。

  建造模式:

  建造模式通常用於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/>');
//someText is a jQuery object referencing an HTMLParagraphElement  

var input = $('<input />');

  看看上面這三個例子,第一個,傳遞了一個<div/>元素附帶一些內容,第二個,傳遞一個空的<p>標籤,第三個,傳遞一個<input/>元素。這三個例子的結果都是一樣的:返回一個jQuery物件的引用指向一個DOM節點。

在jQuery中$變數採用的就是建造模式,例如:返回的jQuery Dom物件可以訪問由jQuery庫提供的所有方法,但是並沒有顯示的呼叫document.createElement,JS 庫通常都是通過這種高階方法處理的。

  想象有多少工作要做,如果我們顯示建立DOM元素然後插入內容到裡面。通過利用建造模式,我們可以專注於物件的型別和內容,而不是顯示的去建立。

  原型模式

  之前,我們討論了在JavaScript中通過函式和新增方法到物件的原型中定義一個資料型別。原型模式通過原型允許物件繼承自其它物件。

  “原型模式是一個基於已經存在的模板物件克隆出新物件的模式”

  在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(),然而,IE8中不支援這個比較新的方法,在這種情況下,我們可以模擬他的行為:

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()時可以指定。不管怎樣,原型模式展示了物件如何繼承自其它物件。

  結構化模式:

  結構化設計模式在當你想理解一個系統如果工作的時候顯得非常有幫助。它能使應用擴充套件方便,維護方便。我們將討論以下兩種模式:組合模式和門面模式

  組合模式:

  組合模式可以理解為一個物件的組合可以像單個物件一樣以一致的方式處理。這是什麼意思呢?好吧,考慮下面這個例子:

$('.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.");
});

  很多JavaScript庫提供了一致的API,不論是處理單個DOM元素還是一個DOM元素的陣列。我們可以新增selected 類給所有含.myList的選擇器的元素。同樣我們可以使用相同的方法處理相似的DOM元素#myItem,類似的,我們可以使用on()方法附上事件處理器在多個節點或單個節點上。

  門面模式:

  隱藏內部複雜結構,提供給使用者簡單介面使用。

  門面模式幾乎總是可以改善大部分軟體的可用性。使用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()在合適的時間被觸發。然而,作為一名開發者,你應該用簡單的介面展示出來。

  總結:設計模式最讓人鼓舞的是有人在過去已經成功實踐了。很多開原始碼實現了各種JavaScript中的是設計模式。作為程式設計師,我們需要意識到每種設計模式的應用場景。我希望這篇教程能幫助一步步回答這些問題。

  英文原文:Tilo Mitra 編譯:伯樂線上–劉志軍

相關文章