觀察者模式
目標者物件和觀察者物件有相互依賴的關系,觀察者對某個物件的狀態進行觀察,如果物件的狀態發生改變,就會通知所有依賴這個物件的觀察者,
目標者物件 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 =https://www.cnblogs.com/zhengrongbaba/archive/2021/10/21/== newVal) return; observe(newVal); // 如果新值是個物件,遞回攔截 value = https://www.cnblogs.com/zhengrongbaba/archive/2021/10/21/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之事件總線
觀察者模式和發布訂閱模式的區別
目標和觀察者之間是互相依賴的,
發布訂閱模式是由統一的調度中心呼叫,發布者和訂閱者不知道對方的存在,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/330058.html
標籤:其他
