使用 es3、es5、es6 實現簡單工廠模式、單例模式、觀察者模式、釋出訂閱模式

溯朝發表於2019-09-30

前言

最近公司晉升崗位裡面的一道小題《使用 es3 實現簡單工廠模式、單例模式、觀察者模式、釋出訂閱模式》,結束之後發現自己還要補好多´д` ;,抽空把裡面幾道小題,重新複習了一遍,擴充到es3、es5、es6,並做了筆記(再次膜拜公司出題大佬)。

完整版github程式碼地址:github.com/xuhuihui/sm…

待做的筆記:

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

簡單工廠模式

定義

定義一個用於建立物件的介面,讓子類決定將哪一個類例項化。Factory Method使一個類的例項化延遲到其子類。

適用範圍

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

關於複雜邏輯的判斷,有兩種解決方案,策略模式和工廠模式。
1、策略模式vs工廠模式的區別
2、策略模式: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程式碼

簡易版程式碼

1、優點:利用initialized屬性,保持永遠只建立一個EasySingletonIns例項,
2、缺點:不夠封閉,可以從外部訪問修改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;
      }
    };
複製程式碼
進階版程式碼

1、優點:利用函式的閉包,阻止了內部屬性被改變。
2、缺點:不好動態地傳入想要的屬性。

    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;
      };
    })();
複製程式碼
酷炫版程式碼

1、優點:利用apply,可以給CoolSingletonCreator例項的prototype增加方法。
2、缺點:建構函式返回非自身例項的情況下,會出現問題。

    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;
    }
}
複製程式碼

觀察者模式

定義

定義物件間的一種一對多的依賴關係,以便當一個物件的狀態發生改變時,所有依賴於它的物件都得到通知並自動重新整理。

適用範圍

1、當觀察的資料物件發生變化時, 自動呼叫相應函式。比如 vue 的雙向繫結;
2、每當呼叫物件裡的某個方法時, 就會呼叫相應'訪問'邏輯。比如給測試框架賦能的 spy 函式;

實現

es3程式碼

1、方法:使用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程式碼

1、方法:使用 Object.defineProperty(obj, props, descriptor) 實現觀察者模式。
2、缺點: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程式碼

1、方法:Proxy/Reflect 是 ES6 引入的新特性, 也可以使用其完成觀察者模式。
2、優點:可以劫持陣列的改變;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 // 呼叫相應函式
複製程式碼

釋出訂閱模式

定義

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

使用 es3、es5、es6 實現簡單工廠模式、單例模式、觀察者模式、釋出訂閱模式

釋出訂閱模式和觀察者模式的差異:

1、在觀察者模式中,觀察者是知道Subject的,Subject一直保持對觀察者進行記錄。然而,在釋出訂閱模式中,釋出者和訂閱者不知道對方的存在。它們只有通過訊息代理進行通訊。
2、在釋出訂閱模式中,元件是鬆散耦合的,正好和觀察者模式相反。
3、觀察者模式大多數時候是同步的,比如當事件觸發,Subject就會去呼叫觀察者的方法。而釋出-訂閱模式大多數時候是非同步的(使用訊息佇列)。
4、觀察者模式需要在單個應用程式地址空間中實現,而釋出-訂閱更像交叉應用模式。

適用範圍

1、範圍:MVC、MVVC的架構、Vue的原始碼實現和一些小遊戲等。
2、優點: 在非同步程式設計中實現更深的解耦。
3、缺點: 建立訂閱者本身要消耗一定的時間和記憶體,而且當你訂閱一個訊息以後,可能此訊息最後都未發生,但是這個訂閱者會始終存在於記憶體中。如果程式中大量使用釋出-訂閱的話,也會使得程式跟蹤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模式)、狀態模式、策略模式、職責鏈模式(責任鏈模式)、訪問者模式。






相關文章