實現一個觀察者模式

前言

觀察者模式也成爲了發佈訂閱模式,如下面三部分組成vue

  1. 發佈者
  2. 訂閱者
  3. 消息隊列

上面介紹了組成可能你還有疑惑,下面就舉一個例子,小明打算去售樓處去買一套房子,銷售小姐告訴他這套住宅暫時沒有房源,小明因而留了手機號碼給他,某一天有房源的時候通知他。 上面例子中,發佈者就是售樓中心,訂閱者就是小明,消息隊列就是小明留的手機號碼,觀察者模式可讓對象鬆耦合在一塊兒。es6

實現

下面使用 ES6 的語法來寫,若是沒有基礎,推薦看一遍 es6 入門再來app

上面介紹了定義,就根據上面的定義編寫一個能夠取消以及支持傳遞參數的觀察者實例框架

const Event = new (class Watch {
  constructor() {
    // 消息隊列
    this.list = {};
  }
  // 訂閱
  subscribe(key, fn) {
    if (!this.list[key]) {
      // 避免重複
      this.list[key] = new Set();
    }
    this.list[key].add(fn);
  }
  // 觸發
  trigger(key, ...args) {
    const v = this.list[key];
    if (!v) {
      return;
    }
    v.forEach(f => f.apply(this, args), this);
  }
  // 刪除,key是必須的
  remove(key, fn) {
    const v = this.list[key];
    if (!v) {
      return false;
    }
    if (v.has(fn)) {
      return v.delete(fn);
    }
    // clear沒有返回值,這裏返回一個true
    return v.clear() || true;
  }
})();
Event.subscribe("abc", function(a) {
  console.log(a); // x
});
Event.trigger("abc", "x");
複製代碼

上面就實現了簡單的觀察者模式,不過能夠觀察上面代碼能夠發現觀察者運行的機制是先訂閱後發佈,有沒有辦法相似於 QQ 消息同樣,能夠接收到離線消息,固然這個離線消息只能接收一次。優化

支持先發布後訂閱

實現的思路很簡單,就是經過一個離線消息隊列,發佈的時候判斷這個離線消息隊列存在麼,若是存在,將消息存放在離線消息隊列,當訂閱的時候若是發現有離線消息隊列就執行一次,以後清空網站

const Event = new (class Watch {
  constructor() {
    // 消息隊列
    this.list = {};
    this.offLine = new Set();
  }
  // 訂閱
  subscribe(key, fn) {
    if (!this.list[key]) {
      // 避免重複
      this.list[key] = new Set();
    }
    this.list[key].add(fn);
    if (this.offLine) {
      this.offLine.forEach(f => f(), this);
    }
    this.offLine = null;
  }
  // 觸發
  trigger(key, ...args) {
    // 關鍵代碼
    const fn = () => {
      const v = this.list[key];
      if (!v) {
        return;
      }
      v.forEach(f => f.apply(this, args), this);
    };
    if (this.offLine) {
      this.offLine.add(fn);
    }
    fn();
  }
  // 刪除,key是必須的
  remove(key, fn) {
    const v = this.list[key];
    if (!v) {
      return false;
    }
    if (v.has(fn)) {
      return v.delete(fn);
    }
    // clear沒有返回值,這裏返回一個true
    return v.clear() || true;
  }
})();
Event.trigger("abc", "x");
Event.subscribe("abc", function(a) {
  console.log(a); // x
});
複製代碼

撒花,一個支持取消和先發布後訂閱的觀察者模式已經實現了,不過仍是有待優化的地方,好比命名,咱們能不能經過Event.created('zhangsan').trigger的形式來調用呢?ui

命名

動手寫以前,咱們先縷清一下頭緒this

  • 命名是否必須,能不能不經過created先調用,好比直接就是Event.trigger發佈以後再訂閱

ok,下面就是針對上面問題實現spa

// 定義一個基礎類,這個類是實現的核心層
class Basics {
  _obj = {};
  _default = "default";
  _created(name = this._default) {
    // 定義消息隊列和離線消息隊列
    const list = {};
    let offLine = new Set();
    const then = this;
    const obj = {
      // 觸發,若是第一次觸發就添加到離線隊列中
      trigger(key, ...rest) {
        const fn = () => {
          const arr = [list, key, ...rest];
          return then._trigger.apply(then, arr);
        };
        if (offLine) {
          offLine.add(fn);
        }
        return fn();
      },
      // 添加訂閱者同時執行離線隊列
      subscribe(key, fn) {
        then._subscribe(...[list, key, fn]);
        if (offLine) {
          offLine.forEach(f => f());
        }
        offLine = null;
      },
      // 刪除,key是必須的
      remove(key, fn) {
        const v = this.list[key];
        if (!v) {
          return false;
        }
        if (v.has(fn)) {
          return v.delete(fn);
        }
        // clear沒有返回值,這裏返回一個true
        return v.clear() || true;
      }
    };
    // 判斷命名來決定返回
    return name
      ? this._obj[name]
        ? this._obj[name]
        : (this._obj[name] = obj)
      : obj;
  }
  // 觸發
  _trigger(l, k, ...args) {
    const v = l[k];
    if (!v || !v.size) {
      return;
    }
    return Array.from(v, f => f.apply(this, args), this);
  }
  _subscribe(list, key, fn) {
    if (!list[key]) {
      list[key] = new Set();
    }
    list[key].add(fn);
  }
}
// 這個是實現類
class Watch extends Basics {
  constructor() {
    // 必須,es6規定
    super();
    this.created = super._created;
  }
  trigger(key, ...rest) {
    const v = this.created();
    v.trigger(key, ...rest);
  }
  subscribe(key, fn) {
    const v = this.created();
    v.subscribe(key, fn);
  }
  remove(key, fn) {
    const v = this.created();
    v.remove(key, fn);
  }
}
const Event = new Watch();
Event.trigger("abc");
Event.subscribe("abc", function() {
  console.log(123);
});
Event.created("zhangsan").subscribe("abc", function(c) {
  console.log(c);
});
Event.created("zhangsan").trigger("abc", 456);
複製代碼

在實際中咱們使用觀察者模式的例子也有不少,好比一個網站,分爲導航和側邊,當用戶信息更新的時候展現部分須要更換就能夠用到,還有咱們用的框架,好比vue、Reactrest

相關文章
相關標籤/搜索