javascript設計模式與應用

javascript設計模式與應用

目錄

前言

設計模式真的不少很複雜,建議有興趣的看我文章最後的連接去學習,要想掌握和熟練應用到項目中絕對不是一蹴而就的,我這篇文章頂多就是一個入門級別的學習,讓你們對設計模式有個概念,我講的也很是簡單,代碼太長我本身都懶得看,因此儘可能舉簡單的例子,說實話,設計模式我啃得也很痛苦,說多了都是淚,哈哈!最後說明一下,文章並未列出23種設計模式,我只按照我以爲重要和使用多的講了上面11個,也並不是全部的模式都寫了實現代碼,後續有時間和必要的話,可能還會更新其餘的模式。javascript

設計模式

構造函數模式

用構造函數來生成對象html

// 實例共享的方法定義在原型上,實例自己的屬性定義在構造函數裏面
function Car( model, year, miles ) { 
  this.model = model;
  this.year = year;
  this.miles = miles; 
}
// 這裏注意要在原型上添加方法,而不是給原型賦值,否則就會丟失原型
Car.prototype.toString = function () {
  return this.model + " has done " + this.miles + " miles";
};
// Usage:
var civic = new Car( "Honda Civic", 2009, 20000 );
var mondeo = new Car( "Ford Mondeo", 2010, 5000 );
 
console.log( civic.toString() );
console.log( mondeo.toString() );
複製代碼

工廠模式(Factory)

  • 定義: 工廠模式是用來建立對象的一種最經常使用的設計模式。咱們不暴露建立對象的具體邏輯,而是將將邏輯封裝在一個函數中,那麼這個函數就能夠被視爲一個工廠。工廠模式根據抽象程度的不一樣能夠分爲:簡單工廠,工廠方法和抽象工廠。除非項目很複雜,不然通常用不到工廠方法和抽象方法
  • 應用:jQuery的$選擇器就是用工廠模式建立的
// 簡單模擬一下jQuery的實現
    class jQuery {
    constructor(selector) {
        let slice = Array.prototype.slice
        let dom = slice.call(document.querySelectorAll(selector))
        let len = dom.length? dom.length: 0
        for (let i = 0; i < len; i++) {
            this[i] = dom[i]          
        }
        this.length = len
        this.selector = selector
    }
    addClass() {    
    }
    ...
}
// $就是一個工廠
window.$ = function(selector) {
    return new jQuery(selector)
}
複製代碼

單例模式(Singleton)

  • 系統中只能有一個實例,一個類只能建立一個實例, 好比登錄框,購物車, vuex和redux的store也是單例
  • 來實現一個單例
class Singleton {
    login() {

    }
}
Singleton.getInstance = (function() {
    let instance
    return function() {
        if(!instance) {
            instance = new Singleton()
        }
        return instance
    }
})()
let obj1 = Singleton.getInstance()
let obj2 = Singleton.getInstance()
console.log(obj1 === obj2) // true
複製代碼

代理模式(Proxy)

  • 使用者無權訪問目標對象, 爲其餘對象提供一種代理以控制對這個對象的訪問, 接口不變
  • 網頁事件代理,jQuery.$.proxy, es6的Proxy
  • es6的代理proxy
  • 在 Vue3.0 中將會經過 Proxy 來替換本來的 Object.defineProperty 來實現數據響應式, 接下來咱們本身用Proxy實現一下吧
let onWatch = (obj, setBind, getLogger) => {
  let handler = {
    get(target, property, receiver) {
      getLogger(target, property);
      return Reflect.get(target, property, receiver);
    },
    set(target, property, value, receiver) {
      setBind(value, property);
      return Reflect.set(target, property, value);
    }
  };
  return new Proxy(obj, handler);
};

let obj = { a: 1 };
let p = onWatch(
  obj,
  (v, property) => {
    console.log(`監聽到屬性${property}改變爲${v}`);
  },
  (target, property) => {
    console.log(`'${property}' = ${target[property]}`);
  }
);
p.a = 2; // 監聽到屬性a改變
p.a; // 'a' = 2

複製代碼

觀察者模式(Observer)

  • 主題和觀察者分離,當主題更新的時候,通知全部的觀察者更新本身
  • 應用:vue的響應式,node的eventEmitter, 咱們來實現一個簡單的EventEmitter
  • 觀察者模式和發佈訂閱者模式仍是有點區別的,在這裏不作區分了
class EventEmitter {
  constructor() {
    this._events = {}; // 維護訂閱者列表
  }
  // 訂閱主題
  on(name, fn) {
    if (name in this._events) {
      // 避免重複訂閱
      if(!this._events[name].find(f => f === fn)) {
        this_events[name].push(fn);
      }
    } else {
      this._events[name] = [];
      this._events[name].push(fn);
    }
  }
  // 發佈主題,相關主題的訂閱者更新
  emit(name, ...arg) {
    if (name in this._events) {
      let events = this._events[name];
      for (let i = 0; i < events.length; i++) {
        events[i](...arg);
      }
    }
  }
  // 取消訂閱
  off(name, fn) {
    if (name in this._events) {
      let index = this._events[name].findIndex(f => f === fn);
      if (index > -1) {
        this._events[name].splice(index, 1);
      }
    }
  }
}
let event = new EventEmitter();
function subFn(data) {
  console.log(data);
}
// 訂閱主題
event.on("vue", subFn);
// 發佈通知
event.emit("vue", "vue3.0要出來了"); // "vue3.0要出來了"
// 取消訂閱
event.off("vue", subFn);
// 再發布通知,就不會打印了
event.emit("vue", "vue3.0立刻要出來了");

複製代碼

適配器模式(Adaptor)

  • 適配器模式(Adapter)是將一個類(對象)的接口(方法或屬性)轉化成客戶但願的另一個接口(方法或屬性),適配器模式使得本來因爲接口不兼容而不能一塊兒工做的那些類(對象)能夠一些工做。
  • 舊接口和用戶分離

裝飾器模式(Decorator)

  • 爲對象添加新功能,不改變原有的結構和功能, 優勢是把類(函數)的核心職責和裝飾功能區分開了
  • ES7已經有了裝飾器

阮一峯的ES6教程中對裝飾器講的很好很全面,你們能夠去看看,連接在此(es6.ruanyifeng.com/#docs/decor…)前端

迭代器模式(Iterator)

  • 提供一種方法順序一個聚合對象中各個元素,而又不暴露該對象內部表示。
  • 迭代器的幾個特色是:
    • 訪問一個聚合對象的內容而無需暴露它的內部表示。
    • 爲遍歷不一樣的集合結構提供一個統一的接口,從而支持一樣的算法在不一樣的集合結構上進行操做。
    • 遍歷的同時更改迭代器所在的集合結構可能會致使問題(好比C#的foreach裏不容許修改item。
  • 應用
    • es6的Iterator es6的有序數據集合(Array, string, Map, Set, generator等)都部署了[Symbol.iterator]屬性, 這個屬性是一個方法,返回一個迭代器,所以均可以用for...of遍歷
    • 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());
});
複製代碼

外觀模式(Facade)

  • 定義
    • 爲子系統中的一組接口提供了一個一致的界面,此模塊定義了一個高層接口,這個接口使得這一子系統更加容易使用。
  • 特色
    • 外觀模式不只簡化類中的接口,並且對接口與調用者也進行了解耦。外觀模式常常被認爲開發者必備,它能夠將一些複雜操做封裝起來,並建立一個簡單的接口用於調用。vue

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

    • 外觀模式的優點是易於使用,並且自己也比較輕量級。但也有缺點 外觀模式被開發者連續使用時會產生必定的性能問題,由於在每次調用時都要檢測功能的可用性node

  • js中ie瀏覽器的事件api和其餘瀏覽器的不一樣,爲了兼容,咱們通常都會封裝一個統一的事件處理函數
var addMyEvent = function (el, event, fn) {
    if (el.addEventListener) {
        el.addEventListener(event, fn, false);
    } else if (el.attachEvent) {
        el.attachEvent('on' + event, fn);
    } else {
        el['on' + event] = fn;
    }
}; 
複製代碼

狀態模式(State)

命令模式(Command)

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

咱們如今有一個汽車管理類, 以下es6

(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;
    },
    // 組織車展
    arrangeViewing: function(model, id) {
      return (
        "You have successfully booked a viewing of " +
        model +
        " ( " +
        id +
        " ) "
      );
    }
  }
})();
// 調用方法
carManager.requestInfo( "Ferrari", "14523" );
carManager.buyVehicle( "Ford Mondeo", "54323" );
carManager.arrangeViewing("Ford Escort", "34232" );
複製代碼

然而在一些狀況下,咱們並不想直接調用對象內部的方法。這樣會增長對象與對象間的依賴。如今咱們來擴展一下這個CarManager, 使其可以接受任何來自包括model和car ID 的CarManager對象的處理請求。根據命令模式的定義,咱們但願實現以下這種功能的調用:面試

CarManager.execute({ commandType: "buyVehicle", operand1: 'Ford Escort', operand2: '453543' });
複製代碼

根據這樣的需求,咱們能夠這樣實現CarManager.execute方法:算法

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

改造之後,調用就簡單多了,以下調用均可以實現vuex

CarManager.execute({ request: "arrangeViewing", model: 'Ferrari', carID: '145523' });
CarManager.execute({ request: "requestInfo", model: 'Ford Mondeo', carID: '543434' });
CarManager.execute({ request: "requestInfo", model: 'Ford Escort', carID: '543434' });
CarManager.execute({ request: "buyVehicle", model: 'Ford Escort', carID: '543434' });
複製代碼

參考資料:

相關文章
相關標籤/搜索