使用 es三、es五、es6 實現簡單工廠模式、單例模式、觀察者模式、發佈訂閱模式

前言

最近公司晉升崗位裏面的一道小題《使用 es3 實現簡單工廠模式、單例模式、觀察者模式、發佈訂閱模式》,結束以後發現本身還要補好多´д` ;,抽空把裏面幾道小題,從新複習了一遍,擴充到es三、es五、es6,並作了筆記(再次膜拜公司出題大佬)。vue

完整版github代碼地址:github.com/xuhuihui/sm…git

待作的筆記:

  1. 實現簡易模板字符串(6分)
    • 模擬實現es6模板字符串的${}便可;
    • 無需考慮特殊符號($、{、})的轉義和嵌套等問題;
  2. 模擬實現簡易版React-Redux

簡單工廠模式

定義

定義一個用於建立對象的接口,讓子類決定將哪個類實例化。Factory Method使一個類的實例化延遲到其子類。es6

適用範圍

適用於複雜邏輯判斷的狀況,例如購物的商品,能夠有上架中、出售中、下單中、出貨中、派送中、到手等一系列複雜邏輯判斷。github

關於複雜邏輯的判斷,有兩種解決方案,策略模式和工廠模式。
一、策略模式vs工廠模式的區別
二、策略模式:JavaScript 複雜判斷的更優雅寫法編程

實現

es3代碼

var createPop = function (type, text) {
      // 建立一個對象,並對該對象作出擴展
      var o = new Object();
      o.content = text;
      o.show = function () {
        //  顯示相同部分
        alert(type + ':' + this.content);
        switch (type) {
          case 'alert':
            // 警示框差別部分
            break;
          case 'confirm':
            // 確認框差別部分
            break;
          case 'prompt':
            // 提示框差別部分
            break;
        }
      };
      return o;
    };
複製代碼

es6代碼

class Image {}
class Link {}
class Text {}

class ElementFactory {
    createElement(type, option){
         const ELEMENT = {
            "image" : Image,
            "text"  : Text,
            "link"  : Link
        }
        let ElementConstructor = ELEMENT(type),
            element = null;
        if(ElementConstructor){
            element = new ElementConstructor(option);
        }
        return element;
    }
}
複製代碼

單例模式

定義

  • 確保只有一個實例
  • 能夠全局訪問

適用範圍

適用於彈框的實現, 全局緩存。
實現彈框的一種作法是先建立好彈框, 而後使之隱藏, 這樣子的話會浪費部分沒必要要的 DOM 開銷, 咱們能夠在須要彈框的時候再進行建立, 同時結合單例模式實現只有一個實例, 從而節省部分 DOM 開銷。設計模式

實現

es3代碼

簡易版代碼

一、優勢:利用initialized屬性,保持永遠只建立一個EasySingletonIns實例,
二、缺點:不夠封閉,能夠從外部訪問修改initialized屬性。數組

var EasySingletonIns = {
      initialized: false,
      // 實例擴展寫在此處:
      belongTo: undefined,
      getBelongTo: function () {
        return EasySingletonIns.belongTo;
      }
    };
    var EasySingleton = {
      getInstance: function () {
        if (EasySingletonIns.initialized) {
          return EasySingletonIns;
        }
        EasySingletonIns.initialized = true;
        // 實例擴展也可寫在此處:
        EasySingletonIns.belongTo = 'EasySingleton';
        return EasySingletonIns;
      }
    };
複製代碼
進階版代碼

一、優勢:利用函數的閉包,阻止了內部屬性被改變。
二、缺點:很差動態地傳入想要的屬性。瀏覽器

var AdvancedSingleton = (function () {
      var ins = null;
      return function () {
        if (ins) {
          return ins;
        }
        if (!(this instanceof AdvancedSingleton)) {
          return new AdvancedSingleton();
        }
        function Inner() {
          // 實例擴展寫在此處:
          this.belongTo = 'AdvancedSingleton @ constructor';         }
        Inner.prototype = this.constructor.prototype;
        // 原型擴展也可寫在此處:
        ins = new Inner();
        return ins;
      };
    })();
複製代碼
酷炫版代碼

一、優勢:利用apply,能夠給CoolSingletonCreator實例的prototype增長方法。
二、缺點:構造函數返回非自身實例的狀況下,會出現問題。緩存

function CoolSingletonCreator(fn) {
      if (typeof fn !== 'function') {
        throw new Error('CoolSingletonCreator fn param must be function!');
      }
      var AdvancedSingletonForCool = (function () {
        var ins = null;
        return function () {
          if (ins) {
            return ins;
          }
          if (!(this instanceof AdvancedSingletonForCool)) {
            return new AdvancedSingletonForCool();
          }
          var args = arguments;
          function Inner() {
            fn.apply(this, args);
          }
          Inner.prototype = this.constructor.prototype;
          ins = new Inner();
          return ins;
        };
      })();
      AdvancedSingletonForCool.prototype = fn.prototype;
      AdvancedSingletonForCool.getInstance = function () {
        // 動態參數:(另外,針對只支持es3語法的瀏覽器的bind方法是沒有的,須要本身實現,不在此處展開)
        var args = [null];
        return new (Function.prototype.bind.apply(AdvancedSingletonForCool, args.concat.apply(args, arguments)))();
      };
      return AdvancedSingletonForCool;
    }
複製代碼

es5代碼

var Singleton = function(name) {
    this.name = name;
    //一個標記,用來判斷是否已將建立了該類的實例
    this.instance = null;
}
// 提供了一個靜態方法,用戶能夠直接在類上調用
Singleton.getInstance = function(name) {
    // 沒有實例化的時候建立一個該類的實例
    if(!this.instance) {
        this.instance = new Singleton(name);
    }
    // 已經實例化了,返回第一次實例化對象的引用
    return this.instance;
}
複製代碼

es6代碼

class Singleton {
    constructor(name) {
        this.name = name;
        this.instance = null;
    }
    // 構造一個廣爲人知的接口,供用戶對該類進行實例化
    static getInstance(name) {
        if(!this.instance) {
            this.instance = new Singleton(name);
        }
        return this.instance;
    }
}
複製代碼

觀察者模式

定義

定義對象間的一種一對多的依賴關係,以便當一個對象的狀態發生改變時,全部依賴於它的對象都獲得通知並自動刷新。bash

適用範圍

一、當觀察的數據對象發生變化時, 自動調用相應函數。好比 vue 的雙向綁定;
二、每當調用對象裏的某個方法時, 就會調用相應'訪問'邏輯。好比給測試框架賦能的 spy 函數;

實現

es3代碼

一、方法:使用prototype綁定函數實現。

var Subject = (function () {
      // 觀察者列表(不是必須的,能夠由Subject本身處理)
      function ObserverList () {
        this.observerList = [];
      }
      ObserverList.prototype.add = function (observer) {
        return this.observerList.push(observer);
      };
      ObserverList.prototype.remove = function (observer) {
        this.observerList = this.observerList.filter(function (item) {return item !== observer;});
      };
      ObserverList.prototype.count = function () {
        return this.observerList.length;
      };
      ObserverList.prototype.get = function (index) {
        return this.observerList[index];
      };
      // 主題
      function Subject () {
        this.observers = new ObserverList();
      }
      Subject.prototype.addObserver = function (observer) {
        this.observers.add(observer);
      };
      Subject.prototype.removeObserver = function (observer) {
        this.observers.remove(observer);
      };
      Subject.prototype.notify = function () {
        var observerCount = this.observers.count();
        for (let i = 0; i < observerCount; i++) {
          var observer = this.observers.get(i);
          observer.update.apply(observer, arguments);
        }
      }
      return Subject;
    })();
複製代碼

es5代碼

一、方法:使用 Object.defineProperty(obj, props, descriptor) 實現觀察者模式。
二、缺點:Object.defineProperty() 不會監測到數組引用不變的操做(好比 push/pop 等);Object.defineProperty() 只能監測到對象的屬性的改變, 即若是有深度嵌套的對象則須要再次給之綁定 Object.defineProperty();

<input id="input" type="text" />
複製代碼
const data = {}
    const input = document.getElementById('input')
    Object.defineProperty(data, 'text', {
      set(value) {
        input.value = value
        this.value = value
      }
    })
    input.onchange = function(e) {
      data.text = e.target.value
    }
複製代碼

es6代碼

一、方法:Proxy/Reflect 是 ES6 引入的新特性, 也可使用其完成觀察者模式。
二、優勢:能夠劫持數組的改變;defineProperty 是對屬性的劫持, Proxy 是對對象的劫持;

var obj = {
  value: 0
}
var proxy = new Proxy(obj, {
  set: function(target, key, value, receiver) { 
    Reflect.set(target, key, value, receiver)
  }
})
proxy.value = 1 // 調用相應函數
複製代碼

發佈訂閱模式

定義

定義:在觀察者模式中間,增長消息代理進行通訊,來實現更更鬆的解耦。


發佈訂閱模式和觀察者模式的差別:

一、在觀察者模式中,觀察者是知道Subject的,Subject一直保持對觀察者進行記錄。然而,在發佈訂閱模式中,發佈者和訂閱者不知道對方的存在。它們只有經過消息代理進行通訊。
二、在發佈訂閱模式中,組件是鬆散耦合的,正好和觀察者模式相反。
三、觀察者模式大多數時候是同步的,好比當事件觸發,Subject就會去調用觀察者的方法。而發佈-訂閱模式大多數時候是異步的(使用消息隊列)。
四、觀察者模式須要在單個應用程序地址空間中實現,而發佈-訂閱更像交叉應用模式。

適用範圍

一、範圍:MVC、MVVC的架構、Vue的源碼實現和一些小遊戲等。
二、優勢: 在異步編程中實現更深的解耦。
三、缺點: 建立訂閱者自己要消耗必定的時間和內存,並且當你訂閱一個消息之後,可能此消息最後都未發生,可是這個訂閱者會始終存在於內存中。若是程序中大量使用發佈-訂閱的話,也會使得程序跟蹤bug變得困難。

實現

es5代碼

var Event = function() {
  this.obj = {}
}

Event.prototype.on = function(eventType, fn) { 
  if (!this.obj[eventType]) {
    this.obj[eventType] = []
  }
  this.obj[eventType].push(fn) // 推入數組
}

Event.prototype.emit = function() {
  var eventType = Array.prototype.shift.call(arguments)
  var arr = this.obj[eventType]
  for (let i = 0; i < arr.length; i++) { //推出調用函數
    arr[i].apply(arr[i], arguments)
  }
}

var ev = new Event()
ev.on('click', function(a) { // 訂閱函數
  console.log(a) // 1
})
ev.emit('click', 1)          // 發佈函數
複製代碼

es6代碼

class Subject {
  constructor() {
    this.subs = []
    this.state = '張三' // 觸發更新的狀態
  }
  getState() {
    return this.state
  }
  setState(state) {
    if (this.state === state) {
      // 發佈者同樣
      return
    }
    this.state = state
    this.notify() // 有更新,觸發通知
  }
  addSub(sub) {
    this.subs.push(sub)
  }
  removeSub(sub) {
    const idx = this.subs.findIndex(i => i === sub)
    if (idx === -1) {
      // 不存在該觀察者
      return
    }
    this.subs.splice(idx, 1)
  }
  notify() {
    this.subs.forEach(sub => {
      sub.update() // 與觀察者原型方法update對應!
    })
  }
}

// 觀察人,至關於訂閱者
class Observer {
  update() {
    console.log('update')
  }
}

// 測試代碼
const subject = new Subject()
const ob = new Observer()
const ob2 = new Observer()
ob2.update = function() {
  //修改update方法,實現不一樣邏輯
  console.log('laifeipeng')
}

//目標添加觀察者了
subject.addSub(ob)
subject.addSub(ob2)

//目標發佈消息調用觀察者的更新方法了
// subject.notify(); // 不使用手動觸發,經過內部狀態的設置來觸發
subject.setState('李四')
複製代碼

JavaScript 中常見設計模式

瞭解到的設計模式以下(゚o゚;;,之後每週有時間大概會補的吧(・_・;。

設計模式分爲三種類型,共24種。

  • 建立型模式:單例模式、抽象工廠模式、建造者模式、工廠模式、原型模式。
  • 結構型模式:適配器模式、橋接模式、裝飾模式、組合模式、外觀模式、享元模式、代理模式。
  • 行爲型模式:模版方法模式、命令模式、迭代器模式、觀察者模式、中介者模式、備忘錄模式、解釋器模式(Interpreter模式)、狀態模式、策略模式、職責鏈模式(責任鏈模式)、訪問者模式。
相關文章
相關標籤/搜索