適配器模式是將一個類(對象)的接口(方法或者屬性)轉化成另一個接口,使得本來因爲接口不兼容而不能一塊兒工做的那些類(對象)能夠一塊兒工做javascript
舉個例子:java
飛機類和火車類,他們都是交通運輸工具,都適用於中長途,但就行駛方式來講,火車是在地上跑的,飛機是在天上飛的。若是要讓火車在天上飛(flying),則能夠複用飛機的飛行功能,但其具體的行駛動做仍是應該在地上跑(running),此時,咱們就能夠建立一個火車的適配器,可以讓火車也支持 flying 方法,但其內部仍是調用的 runningsegmentfault
// 抽象工廠方法(實現子類繼承抽象類的工廠) var VehicleFactory = function (subType, superType) { // 判斷抽象工廠中是否有該抽象類 if (typeof VehicleFactory[superType] === "function") { // 緩存類(寄生式繼承) function F() {} // 繼承父類屬性和方法 F.prototype = VehicleFactory[superType].prototype; // 子類原型繼承父類 var p = new F(); // 將子類constructor指向子類 p.constructor = subType; // 設置子類的原型 subType.prototype = p; } else { // 不存在改抽象類則拋出錯誤 throw new Error("未建立該抽象類"); } }; // 飛機抽象類(抽象類是一種聲明但不能使用的類) VehicleFactory.Airplane = function () {}; VehicleFactory.Airplane.prototype = { flying: function () { throw new Error("該方法未定義!"); }, transportation: function () { throw new Error("該方法未定義!"); }, }; // 火車抽象類 VehicleFactory.Train = function () {}; VehicleFactory.Train.prototype = { running: function () { throw new Error("該方法未定義!"); }, transportation: function () { throw new Error("該方法未定義!"); }, };
// 飛機 var CivilAircraft = function () { VehicleFactory.Airplane.call(this); }; VehicleFactory(CivilAircraft, "Airplane"); // 原型是Airplane CivilAircraft.prototype.transportation = function () { console.log("速度很快的交通工具!"); }; CivilAircraft.prototype.flying = function () { console.log("可以飛起來!"); }; // 火車 var CivilTrain = function () { VehicleFactory.Train.call(this); }; VehicleFactory(CivilTrain, "Train"); // 原型是Train CivilTrain.prototype.transportation = function () { console.log("比飛機慢的交通工具!"); }; CivilTrain.prototype.running = function () { console.log("在地上馳騁!"); };
// 火車適配器 var TrainAdapter = function (oTrain) { VehicleFactory.Airplane.call(this); this.oTrain = oTrain; }; TrainAdapter.prototype = new VehicleFactory.Airplane(); TrainAdapter.prototype.flying = function () { this.oTrain.running(); }; TrainAdapter.prototype.transportation = function () { this.oTrain.transportation(); };
該構造函數接受一個火車的實例對象,而後使用 VehicleFactory.Airplane 進行 apply,其適配器原型是 VehicleFactory.Airplane,而後要從新修改其原型的 flying 方法,以便內部調用 oTrain.running()方法後端
var oCivilAircraft = new CivilAircraft(); var oCivilTrain = new CivilTrain(); var oTrainAdapter = new TrainAdapter(oCivilTrain); //原有的飛機行爲 oCivilAircraft.flying(); // 可以飛起來! oCivilAircraft.transportation(); // 速度很快的交通工具! //原有的火車行爲 oCivilTrain.running(); // 在地上馳騁! oCivilTrain.transportation(); // 比飛機慢的交通工具! //適配器火車的行爲(火車調用飛機的方法名稱) oTrainAdapter.transportation(); // 比飛機慢的交通工具! oTrainAdapter.flying(); // 在地上馳騁!
驗證成功,可是此處也暴露出這種適配器有一個問題,就是火車類本來的 transportation 方法在適配器中也須要重寫一遍。若是須要適配器在繼承火車類的基礎上擴展全部飛機類的行爲時,能夠考慮使用多繼承設計模式
適配器還有一些其餘應用,在《JavaScript設計模式》這本書上是以 A 框架適配 JQuery 框架作介紹的。若是兩個框架的api很是的類似,那麼用 window.A = A = jQuery便可實現兩個框架的適配,這樣能夠在不改變原來代碼的狀況下正確的運行新框架的接口。除此以外,適配器模式還可用於:api
使用一個已經存在的對象,但其方法或屬性接口不符合你的要求;瀏覽器
先後端數據傳遞,把後端數據適配成咱們可用的數據格式再使用;緩存
你想建立一個可複用的對象,該對象能夠與其它不相關的對象或不可見對象(即接口方法或屬性不兼容的對象)協同工做;app
想使用已經存在的對象,可是不能對每個都進行原型繼承以匹配它的接口。對象適配器能夠適配它的父對象接口方法或屬性。框架
裝飾者模式就是在不改變原對象的基礎上,經過對其進行包裝擴展(添加屬性或者方法)使原有對象能夠知足更復雜的需求
舉個例子:
如今有一個前人完成的項目,產品經理說要加新需求,當用戶點擊輸入框時,若是輸入框輸入的內容有限制,那麼在輸入框下提示相應文案,且不一樣的輸入框提示文案不相同。如名字輸入框提示輸入數字字母,電話輸入框提示輸入純數字等等。若是輸入框不少,那麼一條一條查找代碼並進行修改將很是麻煩
這時候咱們可使用裝飾者模式,在原有的功能的基礎上添加一些新功能來知足需求,這時候咱們就不須要重寫或者修改原來定義的方法
// 裝飾者 var decorator = function (input, fn) { // 獲取事件源 var input = document.getElementById(input); // 若是事件源已經綁定事件 if (typeof input.onclick === "function") { // 緩存事件源原有回調函數 var oldClickFn = input.onClick; // 爲事件源定義新的事件 input.onClick = function () { // 事件源原有回調函數 oldClickFn(); // 執行事件源新增回調函數 fn(); }; } else { // 事件源未綁定事件,直接爲事件源添加新增回調函數 input.onClick = fn; } };
此時,咱們在原有基礎上爲項目添加新功能時,能夠不用深刻了解原來的代碼,只要調用這個裝飾者並傳入你新增的方法就能夠了
// 姓名框新增功能 decorator('name_input', function() { console.log('姓名輸入框只能輸入漢字和英文!') }) // 電話框新增功能 decorator('tel_input', function() { console.log('電話輸入框只能輸入純數字!) })
裝飾者模式很簡單,就是對原有對象的屬性和方法的添加。可是裝飾者模式很強大,由於它能夠對原有功能進行擴展,好比在一些框架中對瀏覽器原有方法的擴展再封裝就是用到了裝飾者這個模式。
學習中發現的問題:在適配器模式中,書中寫到
window.A = A = jQuery;
這個連等式是怎麼運行的呢?下面這個程序運行結果是什麼呢?
var a = { n: 1 }; a.x = a = { n: 2 }; console.log(a.x); // 輸出?
上面這個問題也頗有趣,它輸出的答案是undefined,爲何呢?由於在開始運行的時候,它初始化了一個a.x的變量,而且它的值爲undefined,且這個a的值指向的是原來的{ n: 1},而賦值號(=)是從右到左執行,所以此時右邊的a = {n: 2},a指向了一個新的地址,該地址裏面保存的值爲{ n: 2},而後a.x又被賦了一個值爲{n: 2},注意此a非彼a,咱們能夠用一箇中間變量看一下: