這篇博客只是自己對設計模式的理解的備忘~
看完了《JavaScript設計模式》這本書,一直沒有寫博客記錄一下,最近抽出時間來重讀了一下,就順便記錄一下~
如果你只是想粗略了解一下JavaScript的設計模式,繼續讀下去,就好了,如果你想詳細了解的話,推薦湯姆大叔的系列博客 深入理解JavaScript系列
下面有些內容也是摘自湯姆大叔的博客~~
1.Constructor(構造器)模式
聲明一個首字母大寫的function,通過在構造器前面加new關鍵字,實例化一個對象。
可以使用原型來定義函數,原型定義的函數,不同的對象可以共用。
例子
// 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());
2.Module(模塊)模式
可以直接聲明一個module對象。也可以執行一個函數,返回一個module對象,執行函數時可以引入一些變量(如JQuery、Underscore)。第二種方式可以引入私有變量和私有函數。
例子
// 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()
優點:對擁有面向對象背景的開發人員來說更整潔,而且它支持私有數據。
缺點:如果要修改可見性(即公有還是私有)時,必須修改每一個曾經使用過該成員的地方,之后也無法在方法里添加私有成員。
3.Revealing Module(揭示模塊)模式
將公有指針指向私有的函數和屬性上,個人感覺有點像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 } }
優點:可以使腳本語法一致,很容易看出哪些函數和變量可以被公開訪問,可讀性高。
缺點:如果一個私有函數引用了一個共有函數,在將公有函數替換掉后,只是替換了公有指針的指向,私有函數還是用調用之前的函數。
4.Singleton(單例)模式
單例模式限制了類只能實例化一次。在實例不存在的時候,它會通過一個方法創建類的新實例,如果實例已經存在,它會直接返回該對象的引用。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
模式的適用性
- 當類只能有一個實例而且客戶可以從一個眾所周知的訪問點訪問它時。
- 該唯一的實例應該是通過子類化可擴展的,並且客戶應該無需更改代碼就能使用一個擴展的實例時。
5.觀察者(Observer)模式
觀察者模式定義了一種一對多的關系,讓多個觀察者對象同時監聽某一個主題對象,這個主題對象的狀態發生變化時就會通知所有的觀察者對象,使得它們能夠自動更新自己。
例子
// 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; }; } */
觀察者的使用場合就是:當一個對象的改變需要同時改變其它對象,並且它不知道具體有多少對象需要改變的時候,就應該考慮使用觀察者模式。
總的來說,觀察者模式所做的工作就是在解耦,讓耦合的雙方都依賴於抽象,而不是依賴於具體。從而使得各自的變化都不會影響到另一邊的變化。
6.Mediator(中介者)模式
中介者是一種行為設計模式,它允許我們公開一個統一的接口,系統的不同部分可以通過該接口進行通信。如果一個系統的各個組件之間有太多直接關系,可以創建一個控制點,各個組件通過這個控制點進行通訊。簡單點說,就是有個控制中心控制着各個組件之間的通訊,那個控制中心就是中介者。
高級代碼可查看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>
中介者模式一般應用於一組對象已定義良好但是以復雜的方式進行通信的場合,一般情況下,中介者模式很容易在系統中使用,但也容易在系統里誤用,當系統出現了多對多交互復雜的對象群時,先不要急於使用中介者模式,而是要思考一下是不是系統設計有問題。
另外,由於中介者模式把交互復雜性變成了中介者本身的復雜性,所以說中介者對象會比其它任何對象都復雜。
7.Prototype(原型)模式
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; } })();
8.Command(命令)模式
用於將一個請求封裝成一個對象,從而使你可用不同的請求對客戶進行參數化;對請求排隊或者記錄請求日志,以及執行可撤銷的操作。也就是說改模式旨在將函數的調用、請求和操作封裝成一個單一的對象,然后對這個對象進行一系列的處理。此外,可以通過調用實現具體函數的對象來解耦命令對象與接收對象。
例子
// 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對象可以根據需要輕松交換,這被認為是該模式的一個更大的好處。
9.Facade(外觀)模式
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模式時,要了解涉及的任何性能成本,確認是否值得抽象。
10.Factory(工廠)模式
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" });
適用場景
- 當對象或組件設置涉及高復雜性時
- 當需要根據所在的不同環境輕松生成對象的不同實例時
- 當處理很多共享相同屬性的小型對象或組件時
- 在編寫只需要滿足一個API契約(亦稱鴨子類型)的其他對象的實例對象時。對於解耦是很有用的
11.Mixin模式
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的缺點稍有爭議,有些開發人員認為將功能注入對象原型中是一種很糟糕的想法,因為它會導致原型污染和函數起源方面的不確定性。
12.Decorator(裝飾者)模式
通常,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設計模式的博客就先到這里,之后如果學習了新的模式,會及時補充到這篇文章中~~