這篇博客只是本身對設計模式的理解的備忘~html
看完了《JavaScript設計模式》這本書,一直沒有寫博客記錄一下,最近抽出時間來重讀了一下,就順便記錄一下~git
若是你只是想粗略瞭解一下JavaScript的設計模式,繼續讀下去,就行了,若是你想詳細瞭解的話,推薦湯姆大叔的系列博客 深刻理解JavaScript系列github
下面有些內容也是摘自湯姆大叔的博客~~設計模式
聲明一個首字母大寫的function,經過在構造器前面加new關鍵字,實例化一個對象。瀏覽器
可使用原型來定義函數,原型定義的函數,不一樣的對象能夠共用。app
例子dom
// Constructor pattern function Car(modal, year, miles) { this.modal = modal; this.year = year; this.miles = miles; // 這個方法會覆蓋prototype中的toString方法 this.toString = function() { return this.modal + " in object toString function"; } } Car.prototype.toString = function() { return this.modal + " in prototype toString function"; }; var a = new Car("a", 2009, 20000); var b = new Car("b", 2011, 1000); console.log(a.toString()); console.log(b.toString());
能夠直接聲明一個module對象。也能夠執行一個函數,返回一個module對象,執行函數時能夠引入一些變量(如JQuery、Underscore)。第二種方式能夠引入私有變量和私有函數。ide
例子函數
// Module pattern // 引入了jQuery和Underscore var myModule = (function(jQ) { // 私有變量 var privateVar = "private"; // 私有函數 function privateMethod() { jQ("body").html("test"); }; return { // 公有變量 publicVar: "public", // 公有函數 publicMethod: function() { privateVar += "Var"; privateMethod(); console.log(privateVar); } }; })(jQuery); // 調用公有函數 myModule.publicMethod()
優勢:對擁有面向對象背景的開發人員來講更整潔,並且它支持私有數據。性能
缺點:若是要修改可見性(即公有仍是私有)時,必須修改每個曾經使用過該成員的地方,以後也沒法在方法裏添加私有成員。
將公有指針指向私有的函數和屬性上,我的感受有點像Java的對象。
例子
// Revealing Module pattern var myRevealingModule = function() { var privateVar = "Harry", publicVar = "Potter"; function privateFunction() { console.log("Name : " + private); } function publicSetName(strName) { privateVar = strName; } function publicGetName() { privateFunction(); } // 將暴露的公有指針指向到私有函數和屬性上 return { setName: publicSetName, getName: publicGetName, familyName: publicVar } }
優勢:可使腳本語法一致,很容易看出哪些函數和變量能夠被公開訪問,可讀性高。
缺點:若是一個私有函數引用了一個共有函數,在將公有函數替換掉後,只是替換了公有指針的指向,私有函數仍是用調用以前的函數。
單例模式限制了類只能實例化一次。在實例不存在的時候,它會經過一個方法建立類的新實例,若是實例已經存在,它會直接返回該對象的引用。Singleton不一樣於靜態類(或對象),能夠延遲初始化。
在JavaScript中,Singleton充當共享資源命名空間,從全局命名空間中隔離出代碼實現,從而爲函數提供單一訪問點。
例子
// Singleton pattern var mySingleton = (function() { // 實例保持了Singleton的一個引用 var instance; function init() { // Singleton // 私有函數和變量 function privateMethod() { console.log("I am private"); } var privateVar = "I am also private"; var privateRandomNumber = Math.random(); return { // 公有函數和變量 publicMethod: function() { console.log("I am public"); }, publicProperty: "I am also public", getRandomNumber: function() { return privateRandomNumber; } }; }; return { // 獲取Singleton的實例,若是存在就返回,不存在就建立新實例 getInstance: function() { if(!instance) { instance = init(); } return instance; } } })(); var singleA = mySingleton.getInstance(); var singleB = mySingleton.getInstance(); console.log(singleA.getRandomNumber() === singleB.getRandomNumber());// true
模式的適用性
觀察者模式定義了一種一對多的關係,讓多個觀察者對象同時監聽某一個主題對象,這個主題對象的狀態發生變化時就會通知全部的觀察者對象,使得它們可以自動更新本身。
例子
// Observer pattern function Observer() { this.fns = []; } Observer.prototype = { subscribe: function(fn) { this.fns.push(fn); }, unsubscribe: function(fn) { this.fns = this.fns.filter( function(el) { if (el !== fn) { return el; } } ); }, update: function(o, thisObj) { var scope = thisObj || window; this.fns.forEach( function(el) { el.call(scope, o); } ); } }; //測試 var o = new Observer; var f1 = function(data) { console.log('Robbin: ' + data + ', 趕忙幹活了!'); }; var f2 = function(data) { console.log('Randall: ' + data + ', 找他加點工資去!'); }; o.subscribe(f1); o.subscribe(f2); o.update("Tom回來了!") //退訂f1 o.unsubscribe(f1); //再來驗證 o.update("Tom回來了!"); /* // 若是提示找不到filter或者forEach函數,多是由於你的瀏覽器還不夠新,暫時不支持新標準的函數,你可使用以下方式本身定義 if (!Array.prototype.forEach) { Array.prototype.forEach = function (fn, thisObj) { var scope = thisObj || window; for (var i = 0, j = this.length; i < j; ++i) { fn.call(scope, this[i], i, this); } }; } if (!Array.prototype.filter) { Array.prototype.filter = function (fn, thisObj) { var scope = thisObj || window; var a = []; for (var i = 0, j = this.length; i < j; ++i) { if (!fn.call(scope, this[i], i, this)) { continue; } a.push(this[i]); } return a; }; } */
觀察者的使用場合就是:當一個對象的改變須要同時改變其它對象,而且它不知道具體有多少對象須要改變的時候,就應該考慮使用觀察者模式。
總的來講,觀察者模式所作的工做就是在解耦,讓耦合的雙方都依賴於抽象,而不是依賴於具體。從而使得各自的變化都不會影響到另外一邊的變化。
中介者是一種行爲設計模式,它容許咱們公開一個統一的接口,系統的不一樣部分能夠經過該接口進行通訊。若是一個系統的各個組件之間有太多直接關係,能夠建立一個控制點,各個組件經過這個控制點進行通信。簡單點說,就是有個控制中心控制着各個組件之間的通信,那個控制中心就是中介者。
高級代碼可查看Mediator.js https://github.com/ajacksified/Mediator.js
例子
<!doctype html> <html lang="en"> <head> <title>JavaScript Patterns</title> <meta charset="utf-8"> </head> <body> <div id="results"></div> <script> function Player(name) { this.points = 0; this.name = name; } Player.prototype.play = function () { this.points += 1; mediator.played(); }; var scoreboard = { // 顯示內容的容器 element: document.getElementById('results'), // 更新分數顯示 update: function (score) { var i, msg = ''; for (i in score) { if (score.hasOwnProperty(i)) { msg += '<p><strong>' + i + '<\/strong>: '; msg += score[i]; msg += '<\/p>'; } } this.element.innerHTML = msg; } }; var mediator = { // 全部的player players: {}, // 初始化 setup: function () { var players = this.players; players.home = new Player('Home'); players.guest = new Player('Guest'); }, // play之後,更新分數 played: function () { var players = this.players, score = { Home: players.home.points, Guest: players.guest.points }; scoreboard.update(score); }, // 處理用戶按鍵交互 keypress: function (e) { e = e || window.event; // IE if (e.which === 49) { // 數字鍵 "1" mediator.players.home.play(); return; } if (e.which === 48) { // 數字鍵 "0" mediator.players.guest.play(); return; } } }; // go! mediator.setup(); window.onkeypress = mediator.keypress; // 30秒之後結束 setTimeout(function () { window.onkeypress = null; console.log('Game over!'); }, 30000); </script> </body> </html>
中介者模式通常應用於一組對象已定義良好可是以複雜的方式進行通訊的場合,通常狀況下,中介者模式很容易在系統中使用,但也容易在系統裏誤用,當系統出現了多對多交互複雜的對象羣時,先不要急於使用中介者模式,而是要思考一下是否是系統設計有問題。
另外,因爲中介者模式把交互複雜性變成了中介者自己的複雜性,因此說中介者對象會比其它任何對象都複雜。
Prototype模式爲一種基於現有對象模板,經過克隆方式建立對象的模式。
能夠經過Object.create建立一個擁有指定原型和對象的屬性,也能夠經過函數模仿構造函數建立。
例子
// Prototype pattern // 使用Object.create方法 var vehicle = { getModel: function () { console.log('車輛的模具是:' + this.model); } }; // 能夠在Object.create的第二個參數裏使用對象字面量傳入要初始化的額外屬性. // 其語法與Object.defineProperties或Object.defineProperty方法類型。 // 它容許您設定屬性的特性,例如enumerable, writable 或 configurable。 var car = Object.create(vehicle, { 'id': { value: MY_GLOBAL.nextId(), enumerable: true // 默認writable:false, configurable:false }, 'model': { value: '福特', enumerable: true } }); // 模仿一個構造函數 var beget = (function(){ function F() {} return function (proto) { F.prototype = proto; } })();
用於將一個請求封裝成一個對象,從而使你可用不一樣的請求對客戶進行參數化;對請求排隊或者記錄請求日誌,以及執行可撤銷的操做。也就是說改模式旨在將函數的調用、請求和操做封裝成一個單一的對象,而後對這個對象進行一系列的處理。此外,能夠經過調用實現具體函數的對象來解耦命令對象與接收對象。
例子
// Command pattern 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(name) { return CarManager[name] && CarManager[name].apply(CarManager, [].slice.call(arguments, 1)) } // 執行 CarManager.execute("arrangeViewing", "Harry Potter", "10000");
命令模式爲咱們提供了一種分離職責的手段,這些職責包括從執行命令的任意地方發佈命令以及將該職責轉而委託給不一樣對象。
實施明智的、簡單的命令對象把action動做和調用該動做的對象綁定在一塊兒。它們始終包括一個執行操做(如 run()或 execute())。全部具備相同接口的Command對象能夠根據須要輕鬆交換,這被認爲是該模式的一個更大的好處。
Facade模式爲更大的代碼提供了一個方便的高層次的接口,可以隱藏其底層的真實複雜性。能夠把它想成是簡化的API來展現給其餘開發人員,一般是能夠提升可用性的。
但該模式會影響性能,好比說在jQuery中只須要使用$()就能夠取到元素,用戶不須要使用$.getById()或 $.getByClass()等,但在抽象的時候(即實現的時候),就須要作處理,會下降性能。
例子
// Facade pattern var addMyEvent = function(el, ev, fn) { if(el.addEventListener) { // W3C事件模型 el.addEventListener(ev, fn, false); } else if(el.attachEvent) { // IE事件模型 el.attachEvent("on" + ev, fn); } else { // Traditional事件模型 el["on" + ev] = fn; } }
使用Facade模式時,要了解涉及的任何性能成本,確認是否值得抽象。
Factory模式經過提供一個通用接口來建立對象。若是對象建立過程相對比較複雜,這種方法特別有用,例如,若是它強烈依賴於動態因素或應用程序配置的話。
下面是一個抽象工廠的例子
// Factory pattern // 定義Car的構造函數 function Car(options) { // 默認值 this.doors = options.doors || 4; this.state = options.state || "brand new"; this.color = options.color || "sliver"; } // 定義Truck的構造函數 function Truck(options) { this.state = options.state || "used"; this.wheelSize = options.wheelSize || "large"; this.color = options.color || "blue"; } var AbstractVehicleFactory = (function(){ // 存儲車輛類型 var types = []; return { getVehicle: function( type, customizations ) { var Vehicle = types[type]; return (Vehicle) ? new Vehicle(customizations) : null; }, registerVehicle: function( type, Vehicle ) { var proto = Vehicle.prototype; // 能夠加條件判斷註冊知足何種條件的車輛 types[type] = Vehicle; return AbstractVehicleFactory; } } })(); // 用法 AbstractVehicleFactory.registerVehicle("car", Car); AbstractVehicleFactory.registerVehicle("truck", Truck); // 基於抽象車輛類型實例化一個新的car對象 var car = AbstractVehicleFactory.getVehicle("car", { color: "lime green", state: "like new" }); // 同理實例化一個新的truck對象 var truck = AbstractVehicleFactory.getVehicle("truck", { wheelSize: "medium", color: "neon yellow" });
適用場景
Mixin是能夠輕鬆被一個子類或一組子類繼承功能的類,目的是函數複用。
// Mixin pattern // 定義簡單的Car構造函數 var Car = function(settings) { this.model = settings.model || "no modal provided"; this.color = settings.color || "no color provided"; } // Mixin var Mixin = function() {}; Mixin.prototype = { driveForword: function() { console.log("drive forword"); }, driveBackword: function() { console.log("drive backword"); }, driveSideways: function() { console.log("drive sideways"); } }; // 經過一個方法將現有對象擴展到另一個對象上 function augment(receivingClass, givingClass) { // 只提供特定的方法 if(arguments[2]) { for (var i = 2, len = arguments.length; i < len; i++) { receivingClass.prototype[arguments[i]] = givingClass.prototype[arguments[i]]; }; } // 提供全部的方法 else { for (var methodName in givingClass.prototype) { // 確保接收類不包含所處理方法的同名方法 if(!Object.hasOwnProperty(receivingClass.prototype, methodName)) { receivingClass.prototype[methodName] = givingClass.prototype[methodName]; } // 另外一個方式 // if(!receivingClass.prototype[methodName]) { // receivingClass.prototype[methodName] = givingClass.prototype[methodName]; // } }; } } // 給Car構造函數增長"driveForword"和"driveBackword"兩個方法 augment(Car, Mixin, "driveForword", "driveBackword"); // 建立一個新Car var myCar = new Car({ model: "Ford Escort", color: "blue" }); // 測試確保新增方法可用 myCar.driveForword(); myCar.driveBackword(); // 輸出 // drive forword // drive backword // 也能夠經過不聲明特定方法名的形式,將Mixin的全部方法都添加到Car裏 augment(Car, Mixin); var mySportCar = new Car({ model: "Porsche", color: "red" }); mySportCar.driveSideways(); // 輸出 // drive sideways
Mixin有助於減小系統中的重複功能及增長函數複用。當一個應用程序可能須要在各類對象實例中共享行爲時,咱們能夠經過在Mixin中維護這種共享功能並專一於僅實現系統中真正不一樣的功能,來輕鬆避免任何重複。
Mixin的缺點稍有爭議,有些開發人員認爲將功能注入對象原型中是一種很糟糕的想法,由於它會致使原型污染和函數起源方面的不肯定性。
一般,Decorator提供了將行爲動態添加至系統的現有類的能力。其想法是,裝飾自己對於類原有的基本功能來講並非必要的;不然,它就合併到超類自己了。
下面例子只是一個簡單的例子
// Decorator pattern function Vehicle(vehicleType) { this.vehicleType = vehicleType || "car"; this.model = "default"; this.license = "00000-000"; } // 測試基本的Vehicle實例 var testInstance = new Vehicle("car"); console.log(testInstance); // 建立一個Vehicle實例進行裝飾 var truck = new Vehicle("truck"); // 給truck裝飾新功能 truck.setModel = function(modelName) { this.model = modelName; } truck.setColor = function(color) { this.color = color; } // 測試賦值是否正常工做 truck.setModel("CAT"); truck.setColor("blue"); console.log(truck);
對象能夠被新的行爲包裝或裝飾,而後能夠繼續被使用,而沒必要擔憂被修改的基本對象。
jQuery.extend()容許咱們在運行時或者在隨後一個點上動態地將兩個或兩個以上的對象(和它們的屬性)一塊兒擴展(或合併)爲一個單一對象。在這種狀況下,一個目標對象能夠用新功能來裝飾,而不會在源/超類對象中破壞或重寫現有的方法。
JavaScript設計模式的博客就先到這裏,以後若是學習了新的模式,會及時補充到這篇文章中~~