vue中的觀察者模式和發布訂閱者模式



觀察者模式

目標者對象和觀察者對象有相互依賴的關系,觀察者對某個對象的狀態進行觀察,如果對象的狀態發生改變,就會通知所有依賴這個對象的觀察者,

目標者對象 Subject,擁有方法:添加 / 刪除 / 通知 Observer;

觀察者對象 Observer,擁有方法:接收 Subject 狀態變更通知並處理;

目標對象 Subject 狀態變更時,通知所有 Observer。

 

Vue中響應式數據變化是觀察者模式 每個響應式屬性都有dep,dep存放了依賴這個屬性的watcher,watcher是觀測數據變化的函數,如果數據發生變化,dep就會通知所有的觀察者watcher去調用更新方法。因此, 觀察者需要被目標對象收集,目的是通知依賴它的所有觀察者。那為什么watcher也要存放dep呢?是因為當前正在執行的watcher需要知道此時是哪個dep通知了自己。

 

在beforeCreate之后,created之前調用observe(data)初始化響應式數據,以下是簡化版代碼(沒有處理數組的劫持)
class Observer {
    // 需要對value的屬性描述重新定義
    constructor(value) {
      this.walk(value); // 初始化的時候就對數據進行監控
    }
    walk(data) {
      Object.keys(data).forEach((key) => {
        defineReactive(data, key, data[key]);
      });
    }
  }
  
  function defineReactive(data, key, value) {
    // value 可能是一個對象,要遞歸劫持,所以數據不能嵌套太深
    observe(value);
    let dep = new Dep();
    Object.defineProperty(data, key, {
      get() {
        // 如果有 watcher,就讓 watcher 記住 dep,防止產生重復的 dep, 同時 dep 也收集此 watcher
        if (Dep.target) {
          dep.depend();
        }
        return value;
      },
      set(newVal) {
        // 數據沒變動則不處理
        if (value === newVal) return;
        observe(newVal); // 如果新值是個對象,遞歸攔截
        value = newVal; // 設置新的值
        dep.notify(); // 通知收集的 watcher 去更新
      },
    });
}
function observe(data) {
    // 不是對象則不處理,isObject是用來判斷是否為對象的函數
    if (Object.prototype.toString.call(data)!== '[object Object]') return;
    // 通過類來實現數據的觀測,方便擴展,生成實例
    return new Observer(data);
}
observe(data)
 
在created之后,mouted之前調用mountComponent掛載組件,以下是簡化版代碼(沒有處理watch和computed的watcher)
class Dep {
    static target = null
    constructor() {
      this.id = id++;
      this.subs = []; // 存放依賴的watcher
    }
    depend() {
      // 讓正在執行的watcher記錄dep,同時dep也會記錄watcher
      Dep.target.addDep(this);
    }
    addSub(watcher) {
      // 添加觀察者對象
      this.subs.push(watcher);
    }
    notify() {
      // 觸發觀察者對象的更新方法
      this.subs.forEach((watcher) => watcher.update());
    }
}
class Watcher {
    constructor(vm, exprOrFn) {
      this.vm = vm;
      this.deps = [];
      // 用來去重,防止多次取同一數據時存入多個相同dep
      this.depId = new Set();
      // exprOrFn是updateComponent
      this.getter = exprOrFn;
      // 更新頁面
      this.get();
    }
    get() {
      Dep.target = watcher; // 取值之前,收集 watcher
      this.getter.call(this.vm); // 調用updateComponent更新頁面
      Dep.target = null; // 取值完成后,將 watcher 刪除
    }
    // dep.depend執行時調用
    addDep(dep) {
        let id = dep.id;
        let has = this.depId.has(id);
        if (!has) {
            this.depId.add(id);
            // watcher存放dep
            this.deps.push(dep);
            // dep存放watcher
            dep.addSub(this);
        }
    }  
    // 更新頁面方法,dep.notify執行時調用
    update() {
        this.get(); // 一修改數據就渲染更新
    }
}
function mountComponent(vm) {
    // 渲染更新頁面
    let updateComponent = () => {
      let vnode = vm._render(); // 生成虛擬節點 vnode
      vm._update(vnode); // 將vnode轉為真實節點
    };
    // 每個組件都要調用一個渲染 watcher
    new Watcher(vm, updateComponent);
}
mountComponent(vm)

 

發布訂閱模式

基於一個事件中心,接收通知的對象是訂閱者,需要 先訂閱某個事件,觸發事件的對象是發布者,發布者通過觸發事件,通知各個訂閱者。 js中事件綁定,就是發布訂閱模式

發布訂閱模式相比觀察者模式多了個事件中心,訂閱者和發布者不是直接關聯的。

 

vue中的事件總線就是使用的發布訂閱模式

// 事件總線
class Bus {
  constructor() {
    // 用來記錄事件和監聽該事件的數組
    this.listeners = {};
  }
  // 添加指定事件的監聽者
  $on(eventName, handler) {
    this.listeners[eventName].add(handler);
  }
  // 取消監聽事件
  $off(eventName, handler) {
    this.listeners[eventName].delete(handler);
  }
  // 觸發事件
  $emit(eventName, ...args) {
    this.listeners[eventName].forEach((fn) => fn(...args));
  }
}

事件總線的具體使用可以查看這篇vue之事件總線

 

觀察者模式和發布訂閱模式的區別

目標和觀察者之間是互相依賴的。

發布訂閱模式是由統一的調度中心調用,發布者和訂閱者不知道對方的存在。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM