湯姆大叔的深入理解JavaScript讀後感三(設計模式篇)

今天要分享的內容是tom大叔的JavaScript系列的讀書觀後感3

挑選大叔裏面,個人自己喜歡的設計模式實現

第二十五部分,設計模式之單例模式

在傳統開發工程師眼裏,單例就是保證一個類只有一個實例,實現的方法一般是先判斷實例存在與否,如果存在直接返回,如果不存在就創建了再返回,這就確保了一個類只有一個實例對象。在JavaScript裏,單例作爲一個命名空間提供者,從全局命名空間裏提供一個唯一的訪問點來訪問該對象。

function Universe() {

    // 緩存的實例
    var instance = this;

    // 其它內容
    this.start_time = 0;
    this.bang = "Big";

    // 重寫構造函數
    Universe = function () {
        return instance;
    };
}

// 測試
var uni = new Universe();
var uni2 = new Universe();
uni.bang = "123";
console.log(uni === uni2); // true
console.log(uni2.bang); // 123

這個設計模式,簡單明瞭,通過this進行緩存,剩下了去判斷不存在的麻煩

第二十六部分,設計模式之構造函數模式

構造函數用於創建特定類型的對象——不僅聲明瞭使用的對象,構造函數還可以接受參數以便第一次創建對象的時候設置對象的成員值。你可以自定義自己的構造函數,然後在裏面聲明自定義類型對象的屬性或方法。

function Car(model, year, miles) {
    this.model = model;
    this.year = year;
    this.miles = miles;
}

/*
注意:這裏我們使用了Object.prototype.方法名,而不是Object.prototype
主要是用來避免重寫定義原型prototype對象
*/
Car.prototype.output= function () {
    return this.model + "走了" + this.miles + "公里";
};

var tom = new Car("大叔", 2009, 20000);
var dudu = new Car("Dudu", 2010, 5000);

console.log(tom.output());
console.log(dudu.output());

第二十七部分,設計模式之建造者模式

建造者模式可以將一個複雜對象的構建與其表示相分離,使得同樣的構建過程可以創建不同的表示。也就是說如果我們用了建造者模式,那麼用戶就需要指定需要建造的類型就可以得到它們,而具體建造的過程和細節就不需要知道了。


function getBeerById(id, callback) {
    // 使用ID來請求數據,然後返回數據.
    asyncRequest('GET', 'beer.uri?id=' + id, function (resp) {
        // callback調用 response
        callback(resp.responseText);
    });
}

var el = document.querySelector('#test');
el.addEventListener('click', getBeerByIdBridge, false);

function getBeerByIdBridge(e) {
    getBeerById(this.id, function (beer) {
        console.log('Requested Beer: ' + beer);
    });
}

建造者模式主要用於“分步驟構建一個複雜的對象”,在這其中“分步驟”是一個穩定的算法,而複雜對象的各個部分則經常變化,其優點是:建造者模式的“加工工藝”是暴露的,這樣使得建造者模式更加靈活,並且建造者模式解耦了組裝過程和創建具體部件,使得我們不用去關心每個部件是如何組裝的。
也就是說,上面的函數中,getBeerById函數是穩定的,寫了一個構建過程,而具體的構建細節由另外一個回調函數決定

其實,我覺得,這個部分,不屬於java裏面的建造者模式,更是一種模板模式,可能個人理解不同吧,反正大叔的意思,就是這種樣子

第二十八部分,設計模式之工廠模式

什麼時候使用工廠模式

以下幾種情景下工廠模式特別有用:

對象的構建十分複雜
需要依賴具體環境創建不同實例
處理大量具有相同屬性的小對象
var productManager = {};

productManager.createProductA = function () {
    console.log('ProductA');
}

productManager.createProductB = function () {
    console.log('ProductB');
}

productManager.factory = function (typeType) {
    return new productManager[typeType];
}

productManager.factory("createProductA");

一句話,就是根據不同的類型,調用不同的子類,進行實例化一個對象

第二十九部分,設計模式之裝飾者模式

裝飾者用於通過重載方法的形式添加新功能,該模式可以在被裝飾者前面或者後面加上自己的行爲以達到特定的目的。

var tree = {};
tree.decorate = function () {
    console.log('Make sure the tree won\'t fall');
};

tree.getDecorator = function (deco) {
    tree[deco].prototype = this;
    return new tree[deco];
};

tree.RedBalls = function () {
    this.decorate = function () {
        this.RedBalls.prototype.decorate(); // 第7步:先執行原型(這時候是Angel了)的decorate方法
        console.log('Put on some red balls'); // 第8步 再輸出 red
        // 將這2步作爲RedBalls的decorate方法
    }
};

tree.BlueBalls = function () {
    this.decorate = function () {
        this.BlueBalls.prototype.decorate(); // 第1步:先執行原型的decorate方法,也就是tree.decorate()
        console.log('Add blue balls'); // 第2步 再輸出blue
        // 將這2步作爲BlueBalls的decorate方法
    }
};

tree.Angel = function () {
    this.decorate = function () {
        this.Angel.prototype.decorate(); // 第4步:先執行原型(這時候是BlueBalls了)的decorate方法
        console.log('An angel on the top'); // 第5步 再輸出angel
        // 將這2步作爲Angel的decorate方法
    }
};

tree = tree.getDecorator('BlueBalls'); // 第3步:將BlueBalls對象賦給tree,這時候父原型裏的getDecorator依然可用
tree = tree.getDecorator('Angel'); // 第6步:將Angel對象賦給tree,這時候父原型的父原型裏的getDecorator依然可用
tree = tree.getDecorator('RedBalls'); // 第9步:將RedBalls對象賦給tree

tree.decorate(); // 第10步:執行RedBalls對象的decorate方法

這段代碼,主要是在於,
tree.getDecorator 這段函數設計精妙
他讓tree接受一個新的函數,而每次都用這個新函數去替代之前的函數,並且同時讓原型指向前一個,實現了一個
繼承關係,比如A-》B-》C,最開始tree是A,然後,放入一個,就變成B,之後爲C

第三十部分,設計模式之外觀模式

外觀模式不僅簡化類中的接口,而且對接口與調用者也進行了解耦。外觀模式經常被認爲開發者必備,它可以將一些複雜操作封裝起來,並創建一個簡單的接口用於調用。

外觀模式經常被用於JavaScript類庫裏,通過它封裝一些接口用於兼容多瀏覽器,外觀模式可以讓我們間接調用子系統,從而避免因直接訪問子系統而產生不必要的錯誤。

外觀模式的優勢是易於使用,而且本身也比較輕量級。但也有缺點 外觀模式被開發者連續使用時會產生一定的性能問題,因爲在每次調用時都要檢測功能的可用性。

var addMyEvent = function (el, ev, fn) {
    if (el.addEventListener) {
        el.addEventListener(ev, fn, false);
    } else if (el.attachEvent) {
        el.attachEvent('on' + ev, fn);
    } else {
        el['on' + ev] = fn;
    }
}; 
再來一個簡單的例子,說白了就是用一個接口封裝其它的接口:
var mobileEvent = {
    // ...
    stop: function (e) {
        e.preventDefault();
        e.stopPropagation();
    }
    // ...
};

第三十一部分,設計模式之代理模式

代理,顧名思義就是幫助別人做事

// 先聲明美女對象
var girl = function (name) {
    this.name = name;
};

// 這是dudu
var dudu = function (girl) {
    this.girl = girl;
    this.sendGift = function (gift) {
        alert("Hi " + girl.name + ", dudu送你一個禮物:" + gift);
    }
};

// 大叔是代理
var proxyTom = function (girl) {
    this.girl = girl;
    this.sendGift = function (gift) {
        (new dudu(girl)).sendGift(gift); // 替dudu送花咯
    }
};

說的簡單一點,就是代理新類,必須包括被代理的對象,也就是可以主動new

第三十二部分,設計模式之觀察者模式

觀察者模式又叫發佈訂閱模式(Publish/Subscribe),它定義了一種一對多的關係,讓多個觀察者對象同時監聽某一個主題對象,這個主題對象的狀態發生變化時就會通知所有的觀察者對象,使得它們能夠自動更新自己。

jQuery版本

根據jQuery1.7版新增的on/off功能,我們也可以定義jQuery版的觀察者:

(function ($) {

    var o = $({});

    $.subscribe = function () {
        o.on.apply(o, arguments);
    };

    $.unsubscribe = function () {
        o.off.apply(o, arguments);
    };

    $.publish = function () {
        o.trigger.apply(o, arguments);
    };

} (jQuery));

第三十三部分,設計模式之策略模式

策略模式定義了算法家族,分別封裝起來,讓他們之間可以互相替換,此模式讓算法的變化不會影響到使用算法的客戶。

正常的模式,缺點就是,下次繼續增加case進行判斷
        validator = {
            validate: function (value, type) {
                switch (type) {
                    case 'isNonEmpty ':
                        {
                            return true; // NonEmpty 驗證結果
                        }
                    case 'isNumber ':
                        {
                            return true; // Number 驗證結果
                            break;
                        }
                    case 'isAlphaNum ':
                        {
                            return true; // AlphaNum 驗證結果
                        }
                    default:
                        {
                            return true;
                        }
                }
            }
        };
        //  測試
        alert(validator.validate("123", "isNonEmpty"));

策略模式

var validator = {

    // 所有可以的驗證規則處理類存放的地方,後面會單獨定義
    types: {},

    // 驗證類型所對應的錯誤消息
    messages: [],

    // 當然需要使用的驗證類型
    config: {},

    // 暴露的公開驗證方法
    // 傳入的參數是 key => value對
    validate: function (data) {

        var i, msg, type, checker, result_ok;

        // 清空所有的錯誤信息
        this.messages = [];

        for (i in data) {
            if (data.hasOwnProperty(i)) {

                type = this.config[i];  // 根據key查詢是否有存在的驗證規則
                checker = this.types[type]; // 獲取驗證規則的驗證類

                if (!type) {
                    continue; // 如果驗證規則不存在,則不處理
                }
                if (!checker) { // 如果驗證規則類不存在,拋出異常
                    throw {
                        name: "ValidationError",
                        message: "No handler to validate type " + type
                    };
                }

                result_ok = checker.validate(data[i]); // 使用查到到的單個驗證類進行驗證
                if (!result_ok) {
                    msg = "Invalid value for *" + i + "*, " + checker.instructions;
                    this.messages.push(msg);
                }
            }
        }
        return this.hasErrors();
    },

    // helper
    hasErrors: function () {
        return this.messages.length !== 0;
    }
};

然後剩下的工作,就是定義types裏存放的各種驗證類了,我們這裏只舉幾個例子:

// 驗證給定的值是否不爲空
validator.types.isNonEmpty = {
    validate: function (value) {
        return value !== "";
    },
    instructions: "傳入的值不能爲空"
};

// 驗證給定的值是否是數字
validator.types.isNumber = {
    validate: function (value) {
        return !isNaN(value);
    },
    instructions: "傳入的值只能是合法的數字,例如:1, 3.14 or 2010"
};

// 驗證給定的值是否只是字母或數字
validator.types.isAlphaNum = {
    validate: function (value) {
        return !/[^a-z0-9]/i.test(value);
    },
    instructions: "傳入的值只能保護字母和數字,不能包含特殊字符"
};

以配置替代之前的寫死的那種方式,可以理解爲開閉原則,不修改之前的代碼

第三十四部分,設計模式之命令模式

命令模式(Command)的定義是:用於將一個請求封裝成一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或者記錄請求日誌,以及執行可撤銷的操作。也就是說改模式旨在將函數的調用、請求和操作封裝成一個單一的對象,然後對這個對象進行一系列的處理。此外,可以通過調用實現具體函數的對象來解耦命令對象與接收對象。

$(function () {

    var CarManager = {

        // 請求信息
        requestInfo: function (model, id) {
            return 'The information for ' + model +
        ' with ID ' + id + ' is foobar';
        },

        // 購買汽車
        buyVehicle: function (model, id) {
            return 'You have successfully purchased Item '
        + id + ', a ' + model;
        },

        // 組織view
        arrangeViewing: function (model, id) {
            return 'You have successfully booked a viewing of '
        + model + ' ( ' + id + ' ) ';
        }
    };
})();

CarManager.execute = function (command) {
    return CarManager[command.request](command.model, command.carID);
};

CarManager.execute({ request: "arrangeViewing", model: 'Ferrari', carID: '145523' });

之前可能沒明白命令模式,現在看這個例子,就很簡單了,
就是,我不具體的調用函數,而是改成一個命令,通過一個弄命令函數,傳入不同的指令,實現方法的調用
大叔的意思是說,不推薦使用,可以直接調用,就不用這麼麻煩,前天是系統不復雜的時候

第三十五部分,設計模式之迭代器模式

迭代器模式(Iterator):提供一種方法順序一個聚合對象中各個元素,而又不暴露該對象內部表示。

迭代器的幾個特點是:

訪問一個聚合對象的內容而無需暴露它的內部表示。
爲遍歷不同的集合結構提供一個統一的接口,從而支持同樣的算法在不同的集合結構上進行操作。
遍歷的同時更改迭代器所在的集合結構可能會導致問題(比如C#的foreach裏不允許修改item)。

jQuery裏一個非常有名的迭代器就是$.each方法,通過each我們可以傳入額外的function,然後來對所有的item項進行迭代操作,例如:

$.each(['dudu', 'dudu', '酸奶小妹', '那個MM'], function (index, value) {
    console.log(index + ': ' + value);
});
//或者
$('li').each(function (index) {
    console.log(index + ': ' + $(this).text());
});

第三十六部分,設計模式之中介者模式

軟件開發中,中介者是一個行爲設計模式,通過提供一個統一的接口讓系統的不同部分進行通信。一般,如果系統有很多子模塊需要直接溝通,都要創建一箇中央控制點讓其各模塊通過該中央控制點進行交互。中介者模式可以讓這些子模塊不需要直接溝通,而達到進行解耦的目的。

打個比方,平時常見的機場交通控制系統,塔臺就是中介者,它控制着飛機(子模塊)的起飛和降落,因爲所有的溝通都是從飛機向塔臺彙報來完成的,而不是飛機之前相互溝通。中央控制系統就是該系統的關鍵,也就是軟件設計中扮演的中介者角色。

// 如下代碼是僞代碼,請不要過分在意代碼
// 這裏app命名空間就相當於扮演中介者的角色
var app = app || {};

// 通過app中介者來進行Ajax請求
app.sendRequest = function ( options ) {
    return $.ajax($.extend({}, options);
}

// 請求URL以後,展示View
app.populateView = function( url, view ){
  $.when(app.sendRequest({url: url, method: 'GET'})
     .then(function(){
         //顯示內容
     });
}

// 清空內容
app.resetView = function( view ){
   view.html('');
}
之前沒有分清,中介和代理,其實區別是明顯,中介,裏面參與的是多個人,他共同維護這些人,而且還都彼此認識switch 
而代理,只是簡單的處理一個人而已,參與者一

另外,由於中介者模式把交互複雜性變成了中介者本身的複雜性,所以說中介者對象會比其它任何對象都複雜。

第三十七部分,設計模式之享元模式

Flyweight中有兩個重要概念–內部狀態intrinsic和外部狀態extrinsic之分,內部狀態就是在對象裏通過內部方法管理,而外部信息可以在通過外部刪除或者保存。

說白點,就是先捏一個的原始模型,然後隨着不同場合和環境,再產生各具特徵的具體模型,很顯然,在這裏需要產生不同的新對象,所以Flyweight模式中常出現Factory模式,

var Book = function( id, title, author, genre, pageCount,publisherID, ISBN, checkoutDate, checkoutMember, dueReturnDate,availability ){
   this.id = id;
   this.title = title;
   this.author = author;
   this.genre = genre;
   this.pageCount = pageCount;
   this.publisherID = publisherID;
   this.ISBN = ISBN;
   this.checkoutDate = checkoutDate;
   this.checkoutMember = checkoutMember;
   this.dueReturnDate = dueReturnDate;
   this.availability = availability;
};
Book.prototype = {
   getTitle:function(){
       return this.title;
   },
   getAuthor: function(){
       return this.author;
   },
   getISBN: function(){
       return this.ISBN;
   },
/*其它get方法在這裏就不顯示了*/


上面這種函數,最大的問題,就是後面的那4個參數,就算是同一本書,時間不一樣,都算成是不一樣的
其實,你是想知道,書這個東西,而不是書*變量個,這樣內存中有很多沒用的例子


$('div').bind('click', function(){
 console.log('You clicked: ' + $(this).attr('id'));
});
// 上面的代碼,要避免使用,避免再次對DOM元素進行生成jQuery對象,因爲這裏可以直接使用DOM元素自身了。
$('div').bind('click', function(){
 console.log('You clicked: ' + this.id);
});

其實,很簡單,就是一個排列組合的問題
比如,創建文件夾來舉例吧,你是創建book1到book10,10個文件夾,還是,創建book文件夾,裏面放1-10個子文件夾,一個11個文件夾

第一種方案字符串是,5*9+6=51
第二種方案是4+9+2=15
這節省空間是明顯的,別問啥意思

第三十八部分,設計模式之職責鏈模式

職責鏈模式(Chain of responsibility)是使多個對象都有機會處理請求,從而避免請求的發送者和接受者之間的耦合關係。將這個對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有一個對象處理他爲止。

也就是說,請求以後,從第一個對象開始,鏈中收到請求的對象要麼親自處理它,要麼轉發給鏈中的下一個候選者。提交請求的對象並不明確知道哪一個對象將會處理它——也就是該請求有一個隱式的接受者(implicit receiver)。根據運行時刻,任一候選者都可以響應相應的請求,候選者的數目是任意的,你可以在運行時刻決定哪些候選者參與到鏈中。

var NO_TOPIC = -1;
var Topic;

function Handler(s, t) {
    this.successor = s || null;
    this.topic = t || 0;
}

Handler.prototype = {
    handle: function () {
        if (this.successor) {
            this.successor.handle()
        }
    },
    has: function () {
        return this.topic != NO_TOPIC;
    }
};


var app = new Handler({
    handle: function () {
        console.log('app handle');
    }
}, 3);

var dialog = new Handler(app, 1);
dialog.handle = function () {
    console.log('dialog before ...')
    // 這裏做具體的處理操作
    Handler.prototype.handle.call(this); //繼續往上走
    console.log('dialog after ...')
};

var button = new Handler(dialog, 2);
button.handle = function () {
    console.log('button before ...')
    // 這裏做具體的處理操作
    Handler.prototype.handle.call(this);
    console.log('button after ...')
};

button.handle();

通過代碼的運行結果我們可以看出,如果想先自身處理,然後再調用繼任者處理的話,就在末尾執行Handler.prototype.handle.call(this);代碼,如果想先處理繼任者的代碼,就在開頭執行Handler.prototype.handle.call(this);代碼。

prototype用的好,就得理解this和prototype

第三十九部分,設計模式之適配器模式

適配器模式(Adapter)是將一個類(對象)的接口(方法或屬性)轉化成客戶希望的另外一個接口(方法或屬性),適配器模式使得原本由於接口不兼容而不能一起工作的那些類(對象)可以一些工作。速成包裝器(wrapper)。

//鴨子
var Duck = function(){

};
Duck.prototype.fly = function(){
throw new Error("該方法必須被重寫!");
};
Duck.prototype.quack = function(){
throw new Error("該方法必須被重寫!");
}

//火雞
var Turkey = function(){

};
Turkey.prototype.fly = function(){
    throw new Error(" 該方法必須被重寫 !");
};
Turkey.prototype.gobble = function(){
    throw new Error(" 該方法必須被重寫 !");
};



//鴨子
var MallardDuck = function () {
    Duck.apply(this);
};
MallardDuck.prototype = new Duck(); //原型是Duck
MallardDuck.prototype.fly = function () {
    console.log("可以飛翔很長的距離!");
};
MallardDuck.prototype.quack = function () {
    console.log("嘎嘎!嘎嘎!");
};

//火雞
var WildTurkey = function () {
    Turkey.apply(this);
};
WildTurkey.prototype = new Turkey(); //原型是Turkey
WildTurkey.prototype.fly = function () {
    console.log("飛翔的距離貌似有點短!");
};
WildTurkey.prototype.gobble = function () {
    console.log("咯咯!咯咯!");
};
那合適使用適配器模式好呢?如果有以下情況出現時,建議使用:

使用一個已經存在的對象,但其方法或屬性接口不符合你的要求;
你想創建一個可複用的對象,該對象可以與其它不相關的對象或不可見對象(即接口方法或屬性不兼容的對象)協同工作;
想使用已經存在的對象,但是不能對每一個都進行原型繼承以匹配它的接口。對象適配器可以適配它的父對象接口方法或屬性。
另外,適配器模式和其它幾個模式可能容易讓人迷惑,這裏說一下大概的區別:

適配器和橋接模式雖然類似,但橋接的出發點不同,橋接的目的是將接口部分和實現部分分離,從而對他們可以更爲容易也相對獨立的加以改變。而適配器則意味着改變一個已有對象的接口。
裝飾者模式增強了其它對象的功能而同時又不改變它的接口,因此它對應程序的透明性比適配器要好,其結果是裝飾者支持遞歸組合,而純粹使用適配器則是不可能的。
代理模式在不改變它的接口的條件下,爲另外一個對象定義了一個代理。

第四十部分,設計模式之組合模式

組合模式(Composite)將對象組合成樹形結構以表示“部分-整體”的層次結構,組合模式使得用戶對單個對象和組合對象的使用具有一致性。

常見的場景有asp.net裏的控件機制(即control裏可以包含子control,可以遞歸操作、添加、刪除子control),類似的還有DOM的機制,一個DOM節點可以包含子節點,不管是父節點還是子節點都有添加、刪除、遍歷子節點的通用功能。所以說組合模式的關鍵是要有一個抽象類,它既可以表示子元素,又可以表示父元素。

var MenuComponent = function () {
};
MenuComponent.prototype.getName = function () {
    throw new Error("該方法必須重寫!");
};
MenuComponent.prototype.getDescription = function () {
    throw new Error("該方法必須重寫!");
};
MenuComponent.prototype.getPrice = function () {
    throw new Error("該方法必須重寫!");
};
MenuComponent.prototype.isVegetarian = function () {
    throw new Error("該方法必須重寫!");
};
MenuComponent.prototype.print = function () {
    throw new Error("該方法必須重寫!");
};
MenuComponent.prototype.add = function () {
    throw new Error("該方法必須重寫!");
};
MenuComponent.prototype.remove = function () {
    throw new Error("該方法必須重寫!");
};
MenuComponent.prototype.getChild = function () {
    throw new Error("該方法必須重寫!");
};

var MenuItem = function (sName, sDescription, bVegetarian, nPrice) {
    MenuComponent.apply(this);
    this.sName = sName;
    this.sDescription = sDescription;
    this.bVegetarian = bVegetarian;
    this.nPrice = nPrice;
};
MenuItem.prototype = new MenuComponent();
MenuItem.prototype.getName = function () {
    return this.sName;
};
MenuItem.prototype.getDescription = function () {
    return this.sDescription;
};
MenuItem.prototype.getPrice = function () {
    return this.nPrice;
};
MenuItem.prototype.isVegetarian = function () {
    return this.bVegetarian;
};
MenuItem.prototype.print = function () {
    console.log(this.getName() + ": " + this.getDescription() + ", " + this.getPrice() + "euros");
};



var Menu = function (sName, sDescription) {
    MenuComponent.apply(this);
    this.aMenuComponents = [];
    this.sName = sName;
    this.sDescription = sDescription;
    this.createIterator = function () {
        throw new Error("This method must be overwritten!");
    };
};
Menu.prototype = new MenuComponent();
Menu.prototype.add = function (oMenuComponent) {
    // 添加子菜品
    this.aMenuComponents.push(oMenuComponent);
};
Menu.prototype.remove = function (oMenuComponent) {
    // 刪除子菜品
    var aMenuItems = [];
    var nMenuItem = 0;
    var nLenMenuItems = this.aMenuComponents.length;
    var oItem = null;

    for (; nMenuItem < nLenMenuItems; ) {
        oItem = this.aMenuComponents[nMenuItem];
        if (oItem !== oMenuComponent) {
            aMenuItems.push(oItem);
        }
        nMenuItem = nMenuItem + 1;
    }
    this.aMenuComponents = aMenuItems;
};
Menu.prototype.getChild = function (nIndex) {
    //獲取指定的子菜品
    return this.aMenuComponents[nIndex];
};
Menu.prototype.getName = function () {
    return this.sName;
};
Menu.prototype.getDescription = function () {
    return this.sDescription;
};
Menu.prototype.print = function () {
    // 打印當前菜品以及所有的子菜品
    console.log(this.getName() + ": " + this.getDescription());
    console.log("--------------------------------------------");

    var nMenuComponent = 0;
    var nLenMenuComponents = this.aMenuComponents.length;
    var oMenuComponent = null;

    for (; nMenuComponent < nLenMenuComponents; ) {
        oMenuComponent = this.aMenuComponents[nMenuComponent];
        oMenuComponent.print();
        nMenuComponent = nMenuComponent + 1;
    }
};



var DinnerMenu = function () {
    Menu.apply(this);
};
DinnerMenu.prototype = new Menu();

var CafeMenu = function () {
    Menu.apply(this);
};
CafeMenu.prototype = new Menu();

var PancakeHouseMenu = function () {
    Menu.apply(this);
};
PancakeHouseMenu.prototype = new Menu();


var Mattress = function (aMenus) {
    this.aMenus = aMenus;
};
Mattress.prototype.printMenu = function () {
    this.aMenus.print();
};

組合模式,特點就是抽象一個父子公用的類,比如,add,remove這些getChild之類的,必須提供

第四十一部分,設計模式之模板方法

模板方法(TemplateMethod)定義了一個操作中的算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。

模板方法是一種代碼複用的基本技術,在類庫中尤爲重要,因爲他們提取了類庫中的公共行爲。模板方法導致一種反向的控制結構,這種結構就是傳說中的“好萊塢法則”,即“別找找我們,我們找你

var CaffeineBeverage = function () {

};
CaffeineBeverage.prototype.prepareRecipe = function () {
    this.boilWater();
    this.brew();
    this.pourOnCup();
    if (this.customerWantsCondiments()) {
        // 如果可以想加小料,就加上
 this.addCondiments();
    }
};
CaffeineBeverage.prototype.boilWater = function () {
    console.log("將水燒開!");
};
CaffeineBeverage.prototype.pourOnCup = function () {
    console.log("將飲料到再杯子裏!");
};
CaffeineBeverage.prototype.brew = function () {
    throw new Error("該方法必須重寫!");
};
CaffeineBeverage.prototype.addCondiments = function () {
    throw new Error("該方法必須重寫!");
};
// 默認加上小料
CaffeineBeverage.prototype.customerWantsCondiments = function () {
    return true;
};


// 衝咖啡
var Coffee = function () {
    CaffeineBeverage.apply(this);
};
Coffee.prototype = new CaffeineBeverage();
Coffee.prototype.brew = function () {
    console.log("從咖啡機想咖啡倒進去!");
};
Coffee.prototype.addCondiments = function () {
    console.log("添加糖和牛奶");
};
Coffee.prototype.customerWantsCondiments = function () {
    return confirm("你想添加糖和牛奶嗎?");
};

//沖茶葉
var Tea = function () {
    CaffeineBeverage.apply(this);
};
Tea.prototype = new CaffeineBeverage();
Tea.prototype.brew = function () {
    console.log("泡茶葉!");
};
Tea.prototype.addCondiments = function () {
    console.log("添加檸檬!");
};
Tea.prototype.customerWantsCondiments = function () {
    return confirm("你想添加檸檬嘛?");
};

模板方法應用於下列情況:

一次性實現一個算法的不變的部分,並將可變的行爲留給子類來實現
各子類中公共的行爲應被提取出來並集中到一個公共父類中的避免代碼重複,不同之處分離爲新的操作,最後,用一個釣魚這些新操作的模板方法來替換這些不同的代碼
控制子類擴展,模板方法只在特定點調用“hook”操作,這樣就允許在這些點進行擴展

第四十二部分,設計模式之原型模式

原型模式(prototype)是指用原型實例指向創建對象的種類,並且通過拷貝這些原型創建新的對象。


var vehicle = {
    getModel: function () {
        console.log('車輛的模具是:' + this.model);
    }
};

var car = Object.create(vehicle, {
    'id': {
        value: MY_GLOBAL.nextId(),
        enumerable: true // 默認writable:false, configurable:false
 },
    'model': {
        value: '福特',
        enumerable: true
    }
});

說的簡單一點,其實就是特指,JavaScript裏面的原型模式

第四十三部分,設計模式之狀態模式

狀態模式(State)允許一個對象在其內部狀態改變的時候改變它的行爲,對象看起來似乎修改了它的類。
這個地方內容代碼太長了
我自己理解,總結一下

  1. 比如有A,B,C三種狀態
  2. 那麼每種狀態必須對應了一組動作,比如開和關,那麼3*2=6種狀態
  3. 三種狀態,有種轉換關係,狀態的改變,同樣的開,代表的意思不一樣
  4. 比如順序,A-》B-》C-》A,那麼同樣的,動作,開隨着狀態的改變,意義發生變化

第四十四部分,設計模式之橋接模式

橋接模式(Bridge)將抽象部分與它的實現部分分離,使它們都可以獨立地變化。

function getBeerById(id, callback) {
// 通過ID發送請求,然後返回數據
asyncRequest('GET', 'beer.uri?id=' + id, function(resp) {
// callback response
callback(resp.responseText);
});
}
橋接,看大叔的例子,還真是沒懂,回去看了我之前寫過的設計模式,才慢慢理解switch 
橋接=代理+策略
爲什麼這麼說?
1. jdbc是橋接,怎麼用,是不是每次主動輸入一個mysql.jdbc這麼個字符串?
2. 代理模式很明白的只代理一個類,內部寫死了
3. 而策略模式,是有一組策略,客戶端不需要具體使用了什麼策略
4. 那麼橋接,就是你要明確的告訴這個代理,要用哪種策略
5. 因此,你需要清楚,你是要給誰橋?
6. 因此上面這個例子,callback,就是你自己傳遞儘量的,按個策略

第四十五部分,代碼複用模式(避免篇)

寫了6篇,應該讓我們避免的模式,那麼不要去話時間記憶不要用的,重點在下篇

第四十六部分,代碼複用模式(推薦篇)

只需記住,大叔推薦,讓我們記住的模式,就可以了

模式1:原型繼承

原型繼承是讓父對象作爲子對象的原型,從而達到繼承的目的:
function object(o) {
    function F() {
    }

    F.prototype = o;
    return new F();
}

// 要繼承的父對象
var parent = {
    name: "Papa"
};

// 新對象
var child = object(parent);

// 測試
console.log(child.name); // "Papa"


// 父構造函數
function Person() {
    // an "own" property
    this.name = "Adam";
}
// 給原型添加新屬性
Person.prototype.getName = function () {
    return this.name;
};
// 創建新person
var papa = new Person();
// 繼承
var kid = object(papa);
console.log(kid.getName()); // "Adam"


// 父構造函數
function Person() {
    // an "own" property
    this.name = "Adam";
}
// 給原型添加新屬性
Person.prototype.getName = function () {
    return this.name;
};
// 繼承
var kid = object(Person.prototype);
console.log(typeof kid.getName); // "function",因爲是在原型裏定義的
console.log(typeof kid.name); // "undefined", 因爲只繼承了原型
記住一句話,prototype是一個對象

function object(o) {
    function F() {
    }

    F.prototype = o;
    return new F();
}

// 要繼承的父對象
var parent = {
    name: "Papa"
};

// 新對象
var child = object(parent);

// 測試
console.log(child.name); // "Papa"


// 父構造函數
function Person() {
    // an "own" property
    this.name = "Adam";
}
// 給原型添加新屬性
Person.prototype.getName = function () {
    return this.name;
};
// 創建新person
var papa = new Person();
// 繼承
var kid = object(papa);
console.log(kid.getName()); // "Adam"


// 父構造函數
function Person() {
    // an "own" property
    this.name = "Adam";
}
// 給原型添加新屬性
Person.prototype.getName = function () {
    return this.name;
};
// 繼承
var kid = object(Person.prototype);
console.log(typeof kid.getName); // "function",因爲是在原型裏定義的
console.log(typeof kid.name); // "undefined", 因爲只繼承了原型

模式2:複製所有屬性進行繼承

這種方式的繼承就是將父對象裏所有的屬性都複製到子對象上,一般子對象可以使用父對象的數據。

先來看一個淺拷貝的例子:

/* 淺拷貝 */
function extend(parent, child) {
    var i;
    child = child || {};
    for (i in parent) {
        if (parent.hasOwnProperty(i)) {
            child[i] = parent[i];
        }
    }
    return child;
}

var dad = { name: "Adam" };
var kid = extend(dad);
console.log(kid.name); // "Adam"

var dad = {
    counts: [1, 2, 3],
    reads: { paper: true }
};
var kid = extend(dad);
kid.counts.push(4);
console.log(dad.counts.toString()); // "1,2,3,4"
console.log(dad.reads === kid.reads); // true
代碼的最後一行,你可以發現dad和kid的reads是一樣的,也就是他們使用的是同一個引用,這也就是淺拷貝帶來的問題。


/* 深拷貝 */
function extendDeep(parent, child) {
    var i,
        toStr = Object.prototype.toString,
        astr = "[object Array]";

    child = child || {};

    for (i in parent) {
        if (parent.hasOwnProperty(i)) {
            if (typeof parent[i] === 'object') {
                child[i] = (toStr.call(parent[i]) === astr) ? [] : {};
                extendDeep(parent[i], child[i]);
            } else {
                child[i] = parent[i];
            }
        }
    }
    return child;
}

var dad = {
    counts: [1, 2, 3],
    reads: { paper: true }
};
var kid = extendDeep(dad);

kid.counts.push(4);
console.log(kid.counts.toString()); // "1,2,3,4"
console.log(dad.counts.toString()); // "1,2,3"

console.log(dad.reads === kid.reads); // false
kid.reads.paper = false;

這個地方就是厲害在,上面的遞歸那部分,當包含複合部分就會重新遞歸調用一次 


模式3:混合(mix-in)

混入就是將一個對象的一個或多個(或全部)屬性(或方法)複製到另外一個對象,我們舉一個例子:

function mix() {
    var arg, prop, child = {};
    for (arg = 0; arg < arguments.length; arg += 1) {
        for (prop in arguments[arg]) {
            if (arguments[arg].hasOwnProperty(prop)) {
                child[prop] = arguments[arg][prop];
            }
        }
    }
    return child;
}

var cake = mix(
                { eggs: 2, large: true },
                { butter: 1, salted: true },
                { flour: '3 cups' },
                { sugar: 'sure!' }
                );

console.dir(cake);


模式4:借用方法

一個對象借用另外一個對象的一個或兩個方法,而這兩個對象之間不會有什麼直接聯繫。不用多解釋,直接用代碼解釋吧:

var one = {
    name: 'object',
    say: function (greet) {
        return greet + ', ' + this.name;
    }
};

// 測試
console.log(one.say('hi')); // "hi, object"

var two = {
    name: 'another object'
};

console.log(one.say.apply(two, ['hello'])); // "hello, another object"

// 將say賦值給一個變量,this將指向到全局變量
var say = one.say;
console.log(say('hoho')); // "hoho, undefined"

// 傳入一個回調函數callback
var yetanother = {
    name: 'Yet another object',
    method: function (callback) {
        return callback('Hola');
    }
};
console.log(yetanother.method(one.say)); // "Holla, undefined"

function bind(o, m) {
    return function () {
        return m.apply(o, [].slice.call(arguments));
    };
}

var twosay = bind(two, one.say);
console.log(twosay('yo')); // "yo, another object"


// ECMAScript 5給Function.prototype添加了一個bind()方法,以便很容易使用apply()和call()。

if (typeof Function.prototype.bind === 'undefined') {
    Function.prototype.bind = function (thisArg) {
        var fn = this,
slice = Array.prototype.slice,
args = slice.call(arguments, 1);
        return function () {
            return fn.apply(thisArg, args.concat(slice.call(arguments)));
        };
    };
}

var twosay2 = one.say.bind(two);
console.log(twosay2('Bonjour')); // "Bonjour, another object"

var twosay3 = one.say.bind(two, 'Enchanté');
console.log(twosay3()); // "Enchanté, another object"

第四十七部分,對象創建模式(上篇)

模式1:命名空間(namespace)

命名空間可以減少全局命名所需的數量,避免命名衝突或過度。一般我們在進行對象層級定義的時候,經常是這樣的:

var app = app || {};
app.moduleA = app.moduleA || {};

// 更簡潔的方式
var MYAPP = MYAPP || {};


模式2:定義依賴

有時候你的一個模塊或者函數可能要引用第三方的一些模塊或者工具,這時候最好將這些依賴模塊在剛開始的時候就定義好,以便以後可以很方便地替換掉。

var myFunction = function () {
    // 依賴模塊
    var event = YAHOO.util.Event,
        dom = YAHOO.util.dom;

    // 其它函數後面的代碼裏使用局部變量event和dom
};

};
模式3:私有屬性和私有方法

JavaScript本書不提供特定的語法來支持私有屬性和私有方法,但是我們可以通過閉包來實現,代碼如下:


function Gadget() {
    // 私有對象
    var name = 'iPod';
    // 公有函數
    this.getName = function () {
        return name;
    };
}

模式4:Revelation模式

也是關於隱藏私有方法的模式,和《深入理解JavaScript系列(3):全面解析Module模式》裏的Module模式有點類似,但是不是return的方式,而是在外部先聲明一個變量,然後在內部給變量賦值公有方法。代碼如下:

var myarray;

(function () {
    var astr = "[object Array]",
        toString = Object.prototype.toString;

    function isArray(a) {
        return toString.call(a) === astr;
    }

    function indexOf(haystack, needle) {
        var i = 0,
            max = haystack.length;
        for (; i < max; i += 1) {
            if (haystack[i] === needle) {
                return i;
            }
        }
        return -1;
    }

    //通過賦值的方式,將上面所有的細節都隱藏了
    myarray = {
        isArray: isArray,
        indexOf: indexOf,
        inArray: indexOf
    };
} ());

模式5:鏈模式

鏈模式可以你連續可以調用一個對象的方法,比如obj.add(1).remove(2).delete(4).add(2)這樣的形式,其實現思路非常簡單,就是將this原樣返回。代碼如下:

var obj = {
    value: 1,
    increment: function () {
        this.value += 1;
        return this;
    },
    add: function (v) {
        this.value += v;
        return this;
    },
    shout: function () {
        console.log(this.value);
    }
};

第四十八部分,對象創建模式(下篇)

模式6:函數語法糖

函數語法糖是爲一個對象快速添加方法(函數)的擴展,這個主要是利用prototype的特性,代碼比較簡單,我們先來看一下實現代碼:

if (typeof Function.prototype.method !== "function") {
    Function.prototype.method = function (name, implementation) {
        this.prototype[name] = implementation;
        return this;
    };
}
模式7:對象常量

對象常量是在一個對象提供set,get,ifDefined各種方法的體現,而且對於set的方法只會保留最先設置的對象,後期再設置都是無效的,已達到別人無法重載的目的。實現代碼如下:

var constant = (function () {
    var constants = {},
        ownProp = Object.prototype.hasOwnProperty,
    // 只允許設置這三種類型的值
        allowed = {
            string: 1,
            number: 1,
            boolean: 1
        },


模式8:沙盒模式

沙盒(Sandbox)模式即時爲一個或多個模塊提供單獨的上下文環境,而不會影響其他模塊的上下文環境,比如有個Sandbox裏有3個方法event,dom,ajax,在調用其中2個組成一個環境的話,和調用三個組成的環境完全沒有干擾。

模式9:靜態成員

靜態成員(Static Members)只是一個函數或對象提供的靜態屬性,可分爲私有的和公有的,就像C#或Java裏的public static和private static一樣。

我們先來看一下公有成員,公有成員非常簡單,我們平時聲明的方法,函數都是公有的,比如:

// 構造函數
var Gadget = function () {
};

// 公有靜態方法
Gadget.isShiny = function () {
    return "you bet";
};

// 原型上添加的正常方法
Gadget.prototype.setPrice = function (price) {
    this.price = price;
};

// 調用靜態方法
console.log(Gadget.isShiny()); // "you bet"

// 創建實例,然後調用方法
var iphone = new Gadget();
iphone.setPrice(500);


而私有靜態成員,我們可以利用其閉包特性去實現,以下是兩種實現方式。

第一種實現方式:

var Gadget = (function () {
    // 靜態變量/屬性
    var counter = 0;

    // 閉包返回構造函數的新實現
    return function () {
        console.log(counter += 1);
    };
} ()); // 立即執行

var g1 = new Gadget(); // logs 1
var g2 = new Gadget(); // logs 2
var g3 = new Gadget(); // logs 3

第四十九部分,Function模式(上篇)

回調函數

在JavaScript中,當一個函數A作爲另外一個函數B的其中一個參數時,則函數A稱爲回調函數,即A可以在函數B的週期內執行(開始、中間、結束時均可)。


配置對象

如果一個函數(或方法)的參數只有一個參數,並且參數爲對象字面量,我們則稱這種模式爲配置對象模式。例如,如下代碼:

var conf = {
    username:"shichuan",
    first:"Chuan",
    last:"Shi"
};


返回函數

返回函數,則是指在一個函數的返回值爲另外一個函數,或者根據特定的條件靈活創建的新函數,示例代碼如下:

var setup = function () {
    console.log(1);
    return function () {
        console.log(2);
    };
};
// 調用setup 函數
var my = setup(); // 輸出 1
my(); // 輸出 2
// 或者直接調用也可
setup()();

強調一句,這種形式的this,認爲是ao,激活對象,也可以認爲是null,因此是global


偏應用

忽略

Currying

Currying是函數式編程的一個特性,將多個參數的處理轉化成單個參數的處理,類似鏈式調用。

忽略

第五十部分,Function模式(下篇)

立即執行的對象初始化

該模式的意思是指在聲明一個對象(而非函數)的時候,立即執行對象裏的某一個方法來進行初始化工作,通常該模式可以用在一次性執行的代碼上。

({
    // 這裏你可以定義常量,設置其它值
    maxwidth: 600,
    maxheight: 400,

    //  當然也可以定義utility方法
    gimmeMax: function () {
        return this.maxwidth + "x" + this.maxheight;
    },

    // 初始化
    init: function () {
        console.log(this.gimmeMax());
        // 更多代碼...
    }
}).init();  // 這樣就開始初始化咯


分支初始化

分支初始化是指在初始化的時候,根據不同的條件(場景)初始化不同的代碼,也就是所謂的條件語句賦值。之前我們在做事件處理的時候,通常使用類似下面的代碼:

var utils = {
    addListener: function (el, type, fn) {
        if (typeof window.addEventListener === 'function') {
            el.addEventListener(type, fn, false);
        } else if (typeof document.attachEvent !== 'undefined') {
            el.attachEvent('on' + type, fn);
        } else {
            el['on' + type] = fn;
        }
    },
    removeListener: function (el, type, fn) {
    }
};

內存優化

該模式主要是利用函數的屬性特性來避免大量的重複計算。通常代碼形式如下:

var myFunc = function (param) {
    if (!myFunc.cache[param]) {
        var result = {};
        // ... 複雜操作 ...
        myFunc.cache[param] = result;
    }
    return myFunc.cache[param];
};

// cache 存儲
myFunc.cache = {};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章