面試官:你可以手寫Vue2的響應(yīng)式原理嗎?

這道題目是面試中相當(dāng)高頻的一道題目了,但凡你簡歷上有寫:“熟練使用Vue并閱讀過其部分源碼”,那么這道題目十有八九面試官都會去問你。

什么?你簡歷上不寫閱讀過源碼,那面試官也很有可能會問你是否閱讀過響應(yīng)式相關(guān)的源碼

還是那句歌詞唱的:

掙不脫 逃不過
眉頭解不開的結(jié)
命中解不開的劫
image.png

整體流程

作為一個(gè)前端的MVVM框架,Vue的基本思路和Angular、React并無二致,其核心就在于: 當(dāng)數(shù)據(jù)變化時(shí),自動去刷新頁面DOM,這使得我們能從繁瑣的DOM操作中解放出來,從而專心地去處理業(yè)務(wù)邏輯。
這就是Vue的數(shù)據(jù)雙向綁定(又稱響應(yīng)式原理)。數(shù)據(jù)雙向綁定是Vue最獨(dú)特的特性之一。此處我們用官方的一張流程圖來簡要地說明一下Vue響應(yīng)式系統(tǒng)的整個(gè)流程:

image.png

Vue中,每個(gè)組件實(shí)例都有相應(yīng)的watcher實(shí)例對象,它會在組件渲染的過程中把屬性記錄為依賴,之后當(dāng)依賴項(xiàng)的setter被調(diào)用時(shí),會通知watcher重新計(jì)算,從而致使它關(guān)聯(lián)的組件得以更新。

這是一個(gè)典型的觀察者模式。

關(guān)鍵角色

Vue 數(shù)據(jù)雙向綁定的實(shí)現(xiàn)邏輯里,有這樣三個(gè)關(guān)鍵角色:

  • Observer: 它的作用是給對象的屬性添加gettersetter,用于依賴收集和派發(fā)更新
  • Dep: 用于收集當(dāng)前響應(yīng)式對象的依賴關(guān)系,每個(gè)響應(yīng)式對象包括子對象都擁有一個(gè)Dep實(shí)例(里面subsWatcher實(shí)例數(shù)組),當(dāng)數(shù)據(jù)有變更時(shí),會通過dep.notify()通知各個(gè)watcher。
  • Watcher: 觀察者對象 , 實(shí)例分為渲染 watcher (render watcher),計(jì)算屬性 watcher (computed watcher),偵聽器watcher(user watcher)三種

Watcher 和 Dep 的關(guān)系

為什么要單獨(dú)拎出來一小節(jié)專門來說這個(gè)問題呢?因?yàn)榇蟛糠滞瑢W(xué)只是知道:Vue的響應(yīng)式原理是通過Object.defineProperty實(shí)現(xiàn)的。被Object.defineProperty綁定過的對象,會變成「響應(yīng)式」化。也就是改變這個(gè)對象的時(shí)候會觸發(fā)getset事件。
但是對于里面具體的對象依賴關(guān)系并不是很清楚,這樣也就給了面試官一種:你只是背了答案,對于響應(yīng)式的內(nèi)部實(shí)現(xiàn)細(xì)節(jié),你并不是很清楚的印象。
關(guān)于WatcherDep 的關(guān)系這個(gè)問題,其實(shí)剛開始我也不是很清楚,在查閱了相關(guān)資料后,才逐漸對里面的具體實(shí)現(xiàn)有了清晰的理解。

image.png

剛接觸Dep這個(gè)詞的同學(xué)都會比較懵: Dep究竟是用來做什么的呢?我們通過defineReactive方法將data中的數(shù)據(jù)進(jìn)行響應(yīng)式后,雖然可以監(jiān)聽到數(shù)據(jù)的變化了,那我們怎么處理通知視圖就更新呢?
Dep就是幫我們依賴管理的。
如上圖所示:一個(gè)屬性可能有多個(gè)依賴,每個(gè)響應(yīng)式數(shù)據(jù)都有一個(gè)Dep來管理它的依賴。

一段話總結(jié)原理

上面說了那么多,下面我總結(jié)一下Vue響應(yīng)式的核心設(shè)計(jì)思路:
當(dāng)創(chuàng)建Vue實(shí)例時(shí),vue會遍歷data選項(xiàng)的屬性,利用Object.defineProperty為屬性添加gettersetter對數(shù)據(jù)的讀取進(jìn)行劫持(getter用來依賴收集,setter用來派發(fā)更新),并且在內(nèi)部追蹤依賴,在屬性被訪問和修改時(shí)通知變化。
每個(gè)組件實(shí)例會有相應(yīng)的watcher實(shí)例,會在組件渲染的過程中記錄依賴的所有數(shù)據(jù)屬性(進(jìn)行依賴收集,還有computed watcher,user watcher實(shí)例),之后依賴項(xiàng)被改動時(shí),setter方法會通知依賴與此data的watcher實(shí)例重新計(jì)算(派發(fā)更新),從而使它關(guān)聯(lián)的組件重新渲染。
到這里,我們已經(jīng)了解了“套路”,下面讓我們用偽代碼來實(shí)現(xiàn)一下Vue的響應(yīng)式吧!

核心實(shí)現(xiàn)

/**
 * @name Vue數(shù)據(jù)雙向綁定(響應(yīng)式系統(tǒng))的實(shí)現(xiàn)原理
 */

// observe方法遍歷并包裝對象屬性
function observe(target) {
  // 若target是一個(gè)對象,則遍歷它
  if (target && typeof target === "Object") {
    Object.keys(target).forEach((key) => {
      // defineReactive方法會給目標(biāo)屬性裝上“監(jiān)聽器”
      defineReactive(target, key, target[key]);
    });
  }
}
// 定義defineReactive方法
function defineReactive(target, key, val) {
  const dep = new Dep();
  // 屬性值也可能是object類型,這種情況下需要調(diào)用observe進(jìn)行遞歸遍歷
  observe(val);
  // 為當(dāng)前屬性安裝監(jiān)聽器
  Object.defineProperty(target, key, {
    // 可枚舉
    enumerable: true,
    // 不可配置
    configurable: false,
    get: function () {
      return val;
    },
    // 監(jiān)聽器函數(shù)
    set: function (value) {
      dep.notify();
    },
  });
}

class Dep {
  constructor() {
    this.subs = [];
  }

  addSub(sub) {
    this.subs.push(sub);
  }

  notify() {
    this.subs.forEach((sub) => {
      sub.update();
    });
  }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容