理解JavaScript中的設計模式
簡介:可靠的設計模式是可維護軟件的基石,如果你曾參與技術面試,很有可能被問到關于設計模式的這類東西。下面這個指南中,我們將學習一些今天就可以用得著的設計模式。
什么是設計模式?
簡單來說,設計模式就是對特定類型問題重用的軟件解決方案,這些問題在軟件開發的時候經常會碰到,通過很多年的實踐,專家對一些相似地問題總結出一些方法,這些方法就封裝成為一種設計模式,所以:
模式是一種經驗證的用于解決軟件開發問題的方案。
模式是可擴展的,因為他們經常被結構化而且你需要遵循某些規則。
對于相似的問題,模式可被重用
在接下來的教程中,我們將直接給出一些設計模式的例子。
設計模式的種類
軟件開發中,設計模式通常分為幾種類別,在這篇教程中我們重點介紹以下三種:
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()方法。
#p#
處理私有數據
在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元素然后插入內容到里面。通過利用建造模式,我們可以專注于對象的類型和內容,而不是顯示的去創建。
#p#
原型模式
之前,我們討論了在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()方法附上事件處理器在多個節點或單個節點上。
#p#
門面模式:
隱藏內部復雜結構,提供給用戶簡單接口使用。
門面模式幾乎總是可以改善大部分軟件的可用性。使用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