瞭解釋出訂閱模式之後,vue響應式原理竟如此簡單!

小何何同學發表於2020-03-25

哈嘍~~~

最近在複習一些常見的設計模式,聯想到vue的響應式原理(2.x),決定整理一篇淺顯易懂的文章闡述其核心思想。

ok,讓我們開始。

基本概念

釋出訂閱模式基於一個主題事件通道,接收通知的物件可以通過自定義的事件訂閱主題,物件會在主題事件釋出時收到通知。

說到釋出訂閱模式,不得不提觀察者模式,兩者都是維護事件與依賴該事件的物件之間的關係,在有關狀態發生變更時會執行相應的更新,所以也經常有人將兩者混為一談,其實兩者的差異也十分明顯,在釋出訂閱模式中事件統一由排程中心處理,且可以基於不同的主題去執行不同的事件,實現了完全的解耦,更加靈活多變

一個最簡單的釋出訂閱模式示例

let pubSub = {
  subscribers: {},
  subscribe(key, fn) {
    if (!this.subscribers[key]) this.subscribers[key] = [];
    this.subscribers[key].push(fn);
  },
  unSubscribe(key) {
    delete this.subscribers[key];
  },
  publish(key, ...args) {
    let listeners = this.subscribers[key];
    listeners.forEach(fn => fn(...args)); // 通知所有訂閱者   
  }
};
/* 為簡化程式碼,省去了一些錯誤邊界的處理,除錯:
pubSub.subscribe('event', () => {
  console.log('first event');
});
pubSub.subscribe('event', () => {
  console.log('second event');
});
pubSub.publish('event'); // first event second event */
複製程式碼

看到這你大概明白了,釋出訂閱模式就是通過在事件排程中心(subscribers)新增訂閱者的事件(subscribe),等到釋出事件(publish)時執行相應的事件就可以了,接下來,我們看看vue是如何一步步實現響應式系統。

vue響應式原理的實現

data

?是vue官網下載的一張圖,用來描述vue是如何通過收集的依賴,在data變更的時候觸發通知變更的過程。現在看上去還是有點抽象,下面我們從原始碼的角度梳理主要的幾個步驟對其進行一些拆分。

  1. init階段,首先簡單瞭解一下Object.defineProperty(obj, prop, descriptor),descriptor 有一些屬性,其中get和set可以自定義屬性的 Setters 和 Getters使普通物件變成可觀察的物件,在此階段vue中data的所有屬性會被reactive化
// vue/src/core/observer/index.js
const dep = new Dep()  // 例項化依賴管理器 ---- 下面會介紹
function defineReactive (obj,key,val) { // 將物件轉化成可觀察物件
  if (arguments.length === 2) {
    val = obj[key]
  }
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      dep.depend() // 每個data的屬性都會有一個dep物件,用來進行收集依賴
      return value
    },
    set: function reactiveSetter (newVal) {
      if(val === newVal){
         return;
      }
      val = newVal;
      dep.notify() // 通知依賴更新
    }
  })
}

// vue/src/core/observer/dep.js
class Dep { // 訂閱者Dep 用來存放Watcher觀察者物件 也被稱為依賴管理器 
      static target: ?Watcher;
      subs: Array<Watcher>;
      depend () {
        if (Dep.target) {
          Dep.target.addDep(this) // 新增一個Watcher物件
        }
      }
      addSub (sub) {
        this.subs.push(sub)
      }
      notify () {
        const subs = this.subs.slice()
        for (let i = 0, l = subs.length; i < l; i++) {
          subs[i].update() // 通知所有Watcher物件更新檢視
        }
      }
}
複製程式碼
  1. mount階段,我們把Watcher放到這裡說明,因為在此階段會建立一個Watcher類的物件,Watcher就是觀察者,它負責訂閱Dep,每個元件例項都對應一個Watcher例項,依賴收集在此階段完成
// vue/src/core/observer/watcher.js (以下為簡化的程式碼片段)
export default class Watcher {
  constructor (vm,expOrFn,cb) {
    this.vm = vm;
    this.cb = cb;
    this.getter = parsePath(expOrFn) // 這個函式會呼叫元件的render函式來更新虛擬DOM
    this.value = this.get()
  }
  get () {
    Dep.target = this // 將當前的Watcher賦值給了Dep.target
    const vm = this.vm
    let value = this.getter.call(vm, vm) // 呼叫元件的更新函式
    Dep.target = undefined
    return value
  }
  addDep (dep) {
    dep.addSub(this) // 將Watcher物件註冊到該屬性的Dep中 --- 依賴收集
  }
  update () { // 接收到變更時執行更新
    const oldValue = this.value
    this.value = this.get()
    this.cb.call(this.vm, this.value, oldValue)
  }
}
複製程式碼
  1. update階段,當data中屬性發生改變的時候,會呼叫Dep的notify函式,然後通知所有的Watcher呼叫update函式渲染元件,也就是派發更新階段

拆分完成,如果還有不清楚的地方,那下面這張圖形象的解釋了Watcher、Dep和Observer三者之間的關係,結合之前對原始碼的分析,可以看到Observe扮演的角色是釋出者,對物件的每一個屬性進行資料監聽,Watcher是連線元件和data的橋樑,Dep則扮演是一個收集依賴和管理通知更新的排程中心

瞭解釋出訂閱模式之後,vue響應式原理竟如此簡單!

(Tips: 筆者認為弄明白這幾個概念對於理解響應式原理非常重要,在此基礎上閱讀原始碼效果也會好的多。)

總結

本文介紹了釋出訂閱模式的原理和簡單實現,並根據流程梳理簡述了vue響應式原理的核心思想,文章不長,希望對你有所幫助。

時間倉促,文中肯定會有一些不夠嚴謹的地方,歡迎大家指正和討論。

最後,感謝閱讀!

相關文章